Merge changes from topic "ndk-aidl-parcelable-array" am: d38a30a6f9 am: 638282c8c8
am: c23b16a97a

Change-Id: I2b4861efc14064aebd35cda6707f2585598a9541
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/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_read_write.py b/apps/CameraITS/tests/scene0/test_read_write.py
index 1b76806..9905762 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']
@@ -58,55 +52,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..779361c 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,
@@ -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..9f86553 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()
@@ -345,7 +345,7 @@
         if num_features < MIN_FEATURE_PTS:
             print "Not enough feature points in frame", i
             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)
@@ -428,8 +428,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 +452,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/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..c34aac7 100644
--- a/apps/CtsVerifier/Android.mk
+++ b/apps/CtsVerifier/Android.mk
@@ -58,8 +58,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 +83,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..f15a353 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" />
@@ -2346,6 +2319,18 @@
                 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.P2pClientWithConfigTestActivity"
+                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.ServiceRequesterTestListActivity"
                 android:label="@string/p2p_service_discovery_requester"
                 android:configChanges="keyboardHidden|orientation|screenSize" />
@@ -2897,6 +2882,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 +2902,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 +2910,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 +3312,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 +3337,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 +3739,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..adecb7a 100644
--- a/apps/CtsVerifier/proguard.flags
+++ b/apps/CtsVerifier/proguard.flags
@@ -35,7 +35,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/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/values/strings.xml b/apps/CtsVerifier/res/values/strings.xml
index 2ae318e..ed00871 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">
@@ -1424,7 +1438,10 @@
     <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_group_owner_with_config_test">Group Owner With Config 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_service_discovery_responder_test">
         Service Discovery Responder Test</string>
     <string name="p2p_service_discovery_requester_test">
@@ -1831,7 +1848,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>
@@ -1906,6 +1925,40 @@
         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. 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_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. A package installation UI should appear.\n
+        2. 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
@@ -2047,7 +2100,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
@@ -2077,14 +2140,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>
@@ -2109,10 +2174,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>
 
@@ -2528,6 +2591,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>
@@ -2672,6 +2736,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">
@@ -2812,8 +2900,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>
@@ -2870,9 +2959,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>
@@ -2884,7 +2973,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>
@@ -2914,7 +3003,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">
@@ -2930,7 +3019,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>
@@ -2947,7 +3036,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>
@@ -2965,7 +3054,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>
@@ -2982,7 +3071,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>
@@ -3000,7 +3089,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>
@@ -3017,7 +3106,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>
@@ -3310,16 +3399,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>
@@ -3343,8 +3432,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>
@@ -3352,23 +3443,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>
@@ -3738,6 +3827,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>
@@ -3762,11 +3875,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.
@@ -4088,7 +4201,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>
@@ -4112,6 +4224,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>
@@ -4507,6 +4686,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">
@@ -4590,4 +4786,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 07a99da..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);
@@ -66,21 +70,27 @@
                 if (deviceInfo.getChannelCounts().length == 0) {
                     sb.append("Output - No Peripheral Channel Counts\n");
                 } else if (!ListsHelper.isSubset(deviceInfo.getChannelCounts(), attribs.mChannelCounts)) {
-                    sb.append("Output - Channel Counts Mismatch\n");
+                    sb.append("Output - Channel Counts Mismatch" +
+                            " d" + ListsHelper.textFormatDecimal(deviceInfo.getChannelCounts()) +
+                            " p" + ListsHelper.textFormatDecimal(attribs.mChannelCounts) +"\n");
                 }
 
                 // Encodings
                 if (deviceInfo.getEncodings().length == 0) {
                     sb.append("Output - No Peripheral Encodings\n");
                 } else if (!ListsHelper.isSubset(deviceInfo.getEncodings(), attribs.mEncodings)) {
-                    sb.append("Output - Encodings Mismatch\n");
+                    sb.append("Output - Encodings Mismatch" +
+                            " d" + ListsHelper.textFormatHex(deviceInfo.getEncodings()) +
+                            " p" + ListsHelper.textFormatHex(attribs.mEncodings) + "\n");
                 }
 
                 // Sample Rates
                 if (deviceInfo.getSampleRates().length == 0) {
                     sb.append("Output - No Peripheral Sample Rates\n");
                 } else if (!ListsHelper.isSubset(deviceInfo.getSampleRates(), attribs.mSampleRates)) {
-                    sb.append("Output - Sample Rates Mismatch\n");
+                    sb.append("Output - Sample Rates Mismatch" +
+                            " d" + ListsHelper.textFormatHex(deviceInfo.getSampleRates()) +
+                            " p" + ListsHelper.textFormatHex(attribs.mSampleRates) + "\n");
                 }
 
                 // Channel Masks
@@ -91,13 +101,17 @@
                     // Channel Index Masks
                     if (!ListsHelper.isSubset(deviceInfo.getChannelIndexMasks(),
                             attribs.mChannelIndexMasks)) {
-                        sb.append("Output - Channel Index Masks Mismatch\n");
+                        sb.append("Output - Channel Index Masks Mismatch" +
+                                " d" + ListsHelper.textFormatHex(deviceInfo.getChannelIndexMasks()) +
+                                " p" + ListsHelper.textFormatHex(attribs.mChannelIndexMasks) + "\n");
                     }
 
                     // Channel Position Masks
                     if (!ListsHelper.isSubset(deviceInfo.getChannelMasks(),
                             attribs.mChannelPositionMasks)) {
-                        sb.append("Output - Channel Position Masks Mismatch\n");
+                        sb.append("Output - Channel Position Masks Mismatch" +
+                                " d" + ListsHelper.textFormatHex(deviceInfo.getChannelMasks()) +
+                                " p" + ListsHelper.textFormatHex(attribs.mChannelPositionMasks) + "\n");
                     }
                 }
 
@@ -124,21 +138,27 @@
                 if (deviceInfo.getChannelCounts().length == 0) {
                     sb.append("Input - No Peripheral Channel Counts\n");
                 } else if (!ListsHelper.isSubset(deviceInfo.getChannelCounts(), attribs.mChannelCounts)) {
-                    sb.append("Input - Channel Counts Mismatch\n");
+                    sb.append("Input - Channel Counts Mismatch" +
+                            " d" + ListsHelper.textFormatDecimal(deviceInfo.getChannelCounts()) +
+                            " p" + ListsHelper.textFormatDecimal(attribs.mChannelCounts) + "\n");
                 }
 
                 // Encodings
                 if (deviceInfo.getEncodings().length == 0) {
                     sb.append("Input - No Peripheral Encodings\n");
                 } else if (!ListsHelper.isSubset(deviceInfo.getEncodings(), attribs.mEncodings)) {
-                    sb.append("Input - Encodings Mismatch\n");
+                    sb.append("Input - Encodings Mismatch" +
+                            " d" + ListsHelper.textFormatHex(deviceInfo.getEncodings()) +
+                            " p" + ListsHelper.textFormatHex(attribs.mEncodings) + "\n");
                 }
 
                 // Sample Rates
                 if (deviceInfo.getSampleRates().length == 0) {
                     sb.append("Input - No Peripheral Sample Rates\n");
                 } else if (!ListsHelper.isSubset(deviceInfo.getSampleRates(), attribs.mSampleRates)) {
-                    sb.append("Input - Sample Rates Mismatch\n");
+                    sb.append("Input - Sample Rates Mismatch" +
+                            " d" + ListsHelper.textFormatDecimal(deviceInfo.getSampleRates()) +
+                            " p" + ListsHelper.textFormatDecimal(attribs.mSampleRates) + "\n");
                 }
 
                 // Channel Masks
@@ -148,11 +168,15 @@
                 } else {
                     if (!ListsHelper.isSubset(deviceInfo.getChannelIndexMasks(),
                             attribs.mChannelIndexMasks)) {
-                        sb.append("Input - Channel Index Masks Mismatch\n");
+                        sb.append("Input - Channel Index Masks Mismatch" +
+                                " d" + ListsHelper.textFormatHex(deviceInfo.getChannelIndexMasks()) +
+                                " p" + ListsHelper.textFormatHex(attribs.mChannelIndexMasks) + "\n");
                     }
                     if (!ListsHelper.isSubset(deviceInfo.getChannelMasks(),
                             attribs.mChannelPositionMasks)) {
-                        sb.append("Input - Channel Position Masks Mismatch\n");
+                        sb.append("Input - Channel Position Masks Mismatch" +
+                                " d" + ListsHelper.textFormatHex(deviceInfo.getChannelMasks()) +
+                                " p" + ListsHelper.textFormatHex(attribs.mChannelPositionMasks) + "\n");
                     }
                 }
                 if (sb.toString().length() == 0){
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/ListsHelper.java b/apps/CtsVerifier/src/com/android/cts/verifier/audio/peripheralprofile/ListsHelper.java
index 97822d0..565826e 100644
--- a/apps/CtsVerifier/src/com/android/cts/verifier/audio/peripheralprofile/ListsHelper.java
+++ b/apps/CtsVerifier/src/com/android/cts/verifier/audio/peripheralprofile/ListsHelper.java
@@ -60,4 +60,30 @@
 
         return true;
     }
+
+    static public String textFormatHex(int[] list) {
+        StringBuilder sb = new StringBuilder();
+        sb.append("[");
+        for (int index = 0; index < list.length; index++) {
+            sb.append("0x" + Integer.toHexString(list[index]));
+            if (index < list.length-1) {
+                sb.append(", ");
+            }
+        }
+        sb.append("]");
+        return sb.toString();
+    }
+
+    static public String textFormatDecimal(int[] list) {
+        StringBuilder sb = new StringBuilder();
+        sb.append("[");
+        for (int index = 0; index < list.length; index++) {
+            sb.append("0x" + list[index]);
+            if (index < list.length-1) {
+                sb.append(", ");
+            }
+        }
+        sb.append("]");
+        return sb.toString();
+    }
 }
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 a56cd3f..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
@@ -49,31 +49,31 @@
             "<ProfileList Version=\"1.0.0\">" +
             "<PeripheralProfile ProfileName=\"AudioBox USB 96\" ProfileDescription=\"PreSonus AudioBox USB 96\" ProductName=\"USB-Audio - AudioBox USB 96\">" +
                 "<OutputDevInfo ChanCounts=\"2\" ChanPosMasks=\"12\" ChanIndexMasks=\"3\" Encodings=\"4\" SampleRates=\"44100,48000,88200,96000\"/>" +
-                "<InputDevInfo ChanCounts=\"2\" ChanPosMasks=\"12\" ChanIndexMasks=\"3\" Encodings=\"4\" SampleRates=\"44100,48000,88200,96000\"/>" +
+                "<InputDevInfo ChanCounts=\"2\" ChanPosMasks=\"12\" ChanIndexMasks=\"1,3\" Encodings=\"4\" SampleRates=\"44100,48000,88200,96000\"/>" +
             "</PeripheralProfile>" +
-            "<PeripheralProfile ProfileName=\"Audio Interface\" ProfileDescription=\"Presonus AudioVox 44VSL\" ProductName=\"USB-Audio - AudioBox 44 VSL\">" +
-                "<OutputDevInfo ChanCounts=\"2,4\" ChanPosMasks=\"12\" ChanIndexMasks=\"15\" Encodings=\"4\" SampleRates=\"44100,48000,88200,96000\" />" +
-                "<InputDevInfo ChanCounts=\"1,2,4\" ChanPosMasks=\"12,16\" ChanIndexMasks=\"15\" Encodings=\"4\" SampleRates=\"44100,48000,88200,96000\" />" +
+            "<PeripheralProfile ProfileName=\"AudioBox 44VSL\" ProfileDescription=\"Presonus AudioBox 44VSL\" ProductName=\"USB-Audio - AudioBox 44 VSL\">" +
+                "<OutputDevInfo ChanCounts=\"2,3,4\" ChanPosMasks=\"12\" ChanIndexMasks=\"3,7,15\" Encodings=\"4\" SampleRates=\"44100,48000,88200,96000\" />" +
+                "<InputDevInfo ChanCounts=\"1,2,3,4\" ChanPosMasks=\"12,16\" ChanIndexMasks=\"1,3,7,15\" Encodings=\"4\" SampleRates=\"44100,48000,88200,96000\" />" +
             "</PeripheralProfile>" +
             "<PeripheralProfile ProfileName=\"AudioBox 22VSL\" ProfileDescription=\"Presonus AudioBox 22VSL\" ProductName=\"USB-Audio - AudioBox 22 VSL\">" +
                 "<OutputDevInfo ChanCounts=\"2\" ChanPosMasks=\"12\" ChanIndexMasks=\"3\" Encodings=\"4\" SampleRates=\"44100,48000,88200,96000\" />" +
-                "<InputDevInfo ChanCounts=\"1,2\" ChanPosMasks=\"12,16\" ChanIndexMasks=\"3\" Encodings=\"4\" SampleRates=\"44100,48000,88200,96000\" />" +
+                "<InputDevInfo ChanCounts=\"1,2\" ChanPosMasks=\"12,16\" ChanIndexMasks=\"1,3\" Encodings=\"4\" SampleRates=\"44100,48000,88200,96000\" />" +
             "</PeripheralProfile>" +
             "<PeripheralProfile ProfileName=\"AudioBox USB\" ProfileDescription=\"Presonus AudioBox USB\" ProductName=\"USB-Audio - AudioBox USB\">" +
                 "<OutputDevInfo ChanCounts=\"2\" ChanPosMasks=\"12\" ChanIndexMasks=\"3\" Encodings=\"4\" SampleRates=\"44100,48000\" />" +
-                "<InputDevInfo ChanCounts=\"1,2\" ChanPosMasks=\"12,16\" ChanIndexMasks=\"3\" Encodings=\"4\" SampleRates=\"44100,48000\" />" +
+                "<InputDevInfo ChanCounts=\"1,2\" ChanPosMasks=\"12,16\" ChanIndexMasks=\"1,3\" Encodings=\"4\" SampleRates=\"44100,48000\" />" +
             "</PeripheralProfile>" +
             "<PeripheralProfile ProfileName=\"Focusrite 2i4\" ProfileDescription=\"Focusrite Scarlett 2i4\" ProductName=\"USB-Audio - Scarlett 2i4 USB\">" +
                 "<OutputDevInfo ChanCounts=\"2,3,4\" ChanPosMasks=\"12\" ChanIndexMasks=\"3,7,15\" Encodings=\"4\" SampleRates=\"44100,48000,88200,96000\"/>" +
-                "<InputDevInfo ChanCounts=\"1,2\" ChanPosMasks=\"12,16\" ChanIndexMasks=\"3\" Encodings=\"4\" SampleRates=\"44100,48000,88200,96000\"/>" +
+                "<InputDevInfo ChanCounts=\"1,2\" ChanPosMasks=\"12,16\" ChanIndexMasks=\"1,3\" Encodings=\"4\" SampleRates=\"44100,48000,88200,96000\"/>" +
             "</PeripheralProfile>" +
             "<PeripheralProfile ProfileName=\"Behringer UMC204HD\" ProfileDescription=\"Behringer UMC204HD\" ProductName=\"USB-Audio - UMC204HD 192k\">" +
                 "<OutputDevInfo ChanCounts=\"2,4\" ChanPosMasks=\"12\" ChanIndexMasks=\"15\" Encodings=\"2,4\" SampleRates=\"44100,48000,88200,96000,176400,192000\"/>" +
-                "<InputDevInfo ChanCounts=\"2\" ChanPosMasks=\"12\" ChanIndexMasks=\"3\" Encodings=\"4\" SampleRates=\"44100,48000,88200,96000,176400,192000\"/>" +
+                "<InputDevInfo ChanCounts=\"2\" ChanPosMasks=\"12\" ChanIndexMasks=\"1,3\" Encodings=\"4\" SampleRates=\"44100,48000,88200,96000,176400,192000\"/>" +
             "</PeripheralProfile>" +
             "<PeripheralProfile ProfileName=\"Roland Rubix24\" ProfileDescription=\"Roland Rubix24\" ProductName=\"USB-Audio - Rubix24\">" +
                 "<OutputDevInfo ChanCounts=\"2,4\" ChanPosMasks=\"12\" ChanIndexMasks=\"15\" Encodings=\"4\" SampleRates=\"44100,48000,96000,192000\"/>" +
-                "<InputDevInfo ChanCounts=\"2\" ChanPosMasks=\"12\" ChanIndexMasks=\"3\" Encodings=\"4\" SampleRates=\"44100,48000,96000,192000\"/>" +
+                "<InputDevInfo ChanCounts=\"2\" ChanPosMasks=\"12\" ChanIndexMasks=\"1,3\" Encodings=\"4\" SampleRates=\"44100,48000,96000,192000\"/>" +
             "</PeripheralProfile>" +
             "<PeripheralProfile ProfileName=\"Pixel USB-C Dongle + Wired Analog Headset\" ProfileDescription=\"Reference USB Dongle\" ProductName=\"USB-Audio - USB-C to 3.5mm-Headphone Adapte\">" +
                 "<OutputDevInfo ChanCounts=\"2\" ChanPosMasks=\"12\" ChanIndexMasks=\"3\" Encodings=\"4\" SampleRates=\"48000\" />" +
@@ -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..61abeae
--- /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_NO_BIOMETRICS 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_NO_BIOMETRICS) {
+                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 fc2374e..8f762c7 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;
 
@@ -669,6 +674,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);
                 }
@@ -803,6 +810,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");
                     }
@@ -1287,6 +1296,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);
                     }
@@ -1783,6 +1795,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/managedprovisioning/ByodFlowTestActivity.java b/apps/CtsVerifier/src/com/android/cts/verifier/managedprovisioning/ByodFlowTestActivity.java
index 67f82d0..7970992 100644
--- a/apps/CtsVerifier/src/com/android/cts/verifier/managedprovisioning/ByodFlowTestActivity.java
+++ b/apps/CtsVerifier/src/com/android/cts/verifier/managedprovisioning/ByodFlowTestActivity.java
@@ -58,6 +58,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;
@@ -81,6 +82,10 @@
     private DialogTestListItem mAppLinkingTest;
     private DialogTestListItem mDisableNonMarketTest;
     private DialogTestListItem mEnableNonMarketTest;
+    private DialogTestListItem mDisableNonMarketWorkProfileDeviceWideTest;
+    private DialogTestListItem mEnableNonMarketWorkProfileDeviceWideTest;
+    private DialogTestListItem mDisableNonMarketPrimaryUserDeviceWideTest;
+    private DialogTestListItem mEnableNonMarketPrimaryUserDeviceWideTest;
     private DialogTestListItem mWorkNotificationBadgedTest;
     private DialogTestListItem mWorkStatusBarIconTest;
     private DialogTestListItem mWorkStatusBarToastTest;
@@ -106,7 +111,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;
@@ -247,6 +254,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",
@@ -294,6 +307,36 @@
                 new Intent(ByodHelperActivity.ACTION_INSTALL_APK)
                         .putExtra(ByodHelperActivity.EXTRA_ALLOW_NON_MARKET_APPS, true));
 
+        mDisableNonMarketWorkProfileDeviceWideTest = 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));
+
+        mEnableNonMarketWorkProfileDeviceWideTest = 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));
+
+        mDisableNonMarketPrimaryUserDeviceWideTest = 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));
+
+        mEnableNonMarketPrimaryUserDeviceWideTest = 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));
+
         mProfileAccountVisibleTest = new DialogTestListItem(this,
                 R.string.provisioning_byod_profile_visible,
                 "BYOD_ProfileAccountVisibleTest",
@@ -448,6 +491,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,6 +532,7 @@
         adapter.add(mAppSettingsVisibleTest);
         adapter.add(mLocationSettingsVisibleTest);
         adapter.add(mPrintSettingsVisibleTest);
+        adapter.add(mPersonalRingtonesTest);
 
         adapter.add(mCrossProfileIntentFiltersTestFromPersonal);
         adapter.add(mCrossProfileIntentFiltersTestFromWork);
@@ -491,6 +541,10 @@
         */
         adapter.add(mDisableNonMarketTest);
         adapter.add(mEnableNonMarketTest);
+        adapter.add(mDisableNonMarketWorkProfileDeviceWideTest);
+        adapter.add(mEnableNonMarketWorkProfileDeviceWideTest);
+        adapter.add(mDisableNonMarketPrimaryUserDeviceWideTest);
+        adapter.add(mEnableNonMarketPrimaryUserDeviceWideTest);
         adapter.add(mIntentFiltersTest);
         adapter.add(mPermissionLockdownTest);
         adapter.add(mKeyguardDisabledFeaturesTest);
@@ -501,6 +555,7 @@
         adapter.add(mSelectWorkChallenge);
         if (!getPackageManager().hasSystemFeature(PackageManager.FEATURE_AUTOMOTIVE)) {
             adapter.add(mConfirmWorkCredentials);
+            adapter.add(mPatternWorkChallenge);
         }
         adapter.add(mRecentsTest);
         adapter.add(mOrganizationInfoTest);
@@ -656,6 +711,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 0022518..245c366 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/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..87c79d9 100644
--- a/apps/CtsVerifier/src/com/android/cts/verifier/notifications/ConditionProviderVerifierActivity.java
+++ b/apps/CtsVerifier/src/com/android/cts/verifier/notifications/ConditionProviderVerifierActivity.java
@@ -16,8 +16,6 @@
 
 package com.android.cts.verifier.notifications;
 
-import com.android.cts.verifier.R;
-
 import android.app.ActivityManager;
 import android.app.AutomaticZenRule;
 import android.app.NotificationManager;
@@ -26,11 +24,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 +66,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 +285,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 +371,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(NotificationManager.INTERRUPTION_FILTER_PRIORITY);
+            updated1.setZenPolicy(builder.build());
+
+            AutomaticZenRule updated2 = mNm.getAutomaticZenRule(id2);
+            updated2.setName("AfterUpdate2");
+            updated2.setConditionId(MockConditionProvider.toConditionId("afterValue2"));
+            updated2.setInterruptionFilter(NotificationManager.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 +505,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 +517,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 +553,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 +566,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 +576,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 +618,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 +628,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();
@@ -648,9 +797,14 @@
     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),
+                MockConditionProvider.toConditionId(queryValue), policy, true);
     }
 
     private boolean compareRules(AutomaticZenRule rule1, AutomaticZenRule rule2) {
@@ -658,7 +812,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/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/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..5fd62f5 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,16 @@
                 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_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..5b79b8c 100644
--- a/apps/CtsVerifier/src/com/android/cts/verifier/p2p/RequesterTestActivity.java
+++ b/apps/CtsVerifier/src/com/android/cts/verifier/p2p/RequesterTestActivity.java
@@ -212,6 +212,14 @@
     }
 
     /**
+     * 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;
+    }
+
+    /**
      * Search devices and show the found devices on the dialog.
      * After user selection, the specified test will be executed.
      */
@@ -245,7 +253,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..bc23459 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,96 @@
 
         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.
+         */
+        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;
+        }
+
+        /*
+         * Wait until peer gets marked conencted.
+         */
+        notifyTestMsg(R.string.p2p_waiting_for_peer_to_connect);
+        if (mReceiverTest.waitPeerConnected(mTargetAddress, TIMEOUT) != true) {
+            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..b1d28cd
--- /dev/null
+++ b/apps/CtsVerifier/src/com/android/cts/verifier/p2p/testcase/GoWithConfigTestCase.java
@@ -0,0 +1,131 @@
+/*
+ * Copyright (C) 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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;
+
+    public GoWithConfigTestCase(Context context) {
+        super(context);
+    }
+
+    @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 config = new WifiP2pConfig.Builder()
+                .setNetworkName("DIRECT-XY-HELLO")
+                .setPassphrase("DEADBEEF")
+                .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() {
+        return "Accept client connection test";
+    }
+}
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/P2pClientConfigTestCase.java b/apps/CtsVerifier/src/com/android/cts/verifier/p2p/testcase/P2pClientConfigTestCase.java
new file mode 100644
index 0000000..90a77fc
--- /dev/null
+++ b/apps/CtsVerifier/src/com/android/cts/verifier/p2p/testcase/P2pClientConfigTestCase.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.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 {
+
+    public P2pClientConfigTestCase(Context context) {
+        super(context);
+    }
+
+    @Override
+    protected boolean executeTest() throws InterruptedException {
+
+        WifiP2pConfig config = new WifiP2pConfig.Builder()
+                .setNetworkName("DIRECT-XY-HELLO")
+                .setPassphrase("DEADBEEF")
+                .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() {
+        return "Join p2p group test (config)";
+    }
+}
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..7fece23 100644
--- a/apps/CtsVerifier/src/com/android/cts/verifier/security/BiometricPromptBoundKeysTest.java
+++ b/apps/CtsVerifier/src/com/android/cts/verifier/security/BiometricPromptBoundKeysTest.java
@@ -22,6 +22,8 @@
 import android.os.Handler;
 import android.os.Looper;
 
+import com.android.cts.verifier.R;
+
 import java.util.concurrent.Executor;
 
 public class BiometricPromptBoundKeysTest extends FingerprintBoundKeysTest {
@@ -63,7 +65,7 @@
         mCancellationSignal = new CancellationSignal();
         mDialogCallback = new DialogCallback();
         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) {
@@ -76,4 +78,14 @@
                 .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..1ae7a12 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;
@@ -76,12 +75,20 @@
     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);
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/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/api23/Android.mk b/apps/VpnApp/api23/Android.mk
index 9f3f2e7..3167888 100644
--- a/apps/VpnApp/api23/Android.mk
+++ b/apps/VpnApp/api23/Android.mk
@@ -27,6 +27,7 @@
 LOCAL_RESOURCE_DIR := $(LOCAL_PATH)/../res
 
 LOCAL_SDK_VERSION := current
+LOCAL_MIN_SDK_VERSION := 22
 
 # tag this module as a cts test artifact
 LOCAL_COMPATIBILITY_SUITE := cts vts general-tests
diff --git a/apps/VpnApp/api24/Android.mk b/apps/VpnApp/api24/Android.mk
index fc2761c..d7fb82c 100644
--- a/apps/VpnApp/api24/Android.mk
+++ b/apps/VpnApp/api24/Android.mk
@@ -27,6 +27,7 @@
 LOCAL_RESOURCE_DIR := $(LOCAL_PATH)/../res
 
 LOCAL_SDK_VERSION := current
+LOCAL_MIN_SDK_VERSION := 22
 
 # tag this module as a cts test artifact
 LOCAL_COMPATIBILITY_SUITE := cts vts general-tests
diff --git a/apps/VpnApp/latest/AndroidManifest.xml b/apps/VpnApp/latest/AndroidManifest.xml
index f81e381..418726a 100644
--- a/apps/VpnApp/latest/AndroidManifest.xml
+++ b/apps/VpnApp/latest/AndroidManifest.xml
@@ -17,9 +17,7 @@
 <manifest xmlns:android="http://schemas.android.com/apk/res/android"
         package="com.android.cts.vpnfirewall">
 
-    <uses-sdk android:minSdkVersion="22"/>
     <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/apps/VpnApp/notalwayson/AndroidManifest.xml b/apps/VpnApp/notalwayson/AndroidManifest.xml
index b356369..4b9184e 100644
--- a/apps/VpnApp/notalwayson/AndroidManifest.xml
+++ b/apps/VpnApp/notalwayson/AndroidManifest.xml
@@ -17,7 +17,6 @@
 <manifest xmlns:android="http://schemas.android.com/apk/res/android"
         package="com.android.cts.vpnfirewall">
 
-    <uses-sdk android:minSdkVersion="22"/>
     <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE"/>
     <uses-permission android:name="android.permission.FOREGROUND_SERVICE"/>
 
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/util/Android.bp b/common/device-side/util/Android.bp
index 2000cb8..7efdc30 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",
     ],
 
@@ -27,10 +27,13 @@
         "ub-uiautomator",
         "mockito-target-minus-junit4",
         "androidx.annotation_annotation",
+        "collector-device-lib",
     ],
 
     libs: [
         "android.test.runner.stubs",
         "android.test.base.stubs",
     ],
-}
+
+    jarjar_rules: "protobuf-jarjar-rules.txt",
+}
\ No newline at end of file
diff --git a/common/device-side/util/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/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/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..ed45ef4 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;
@@ -55,7 +57,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 +65,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 +92,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 +115,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..b858f5c
--- /dev/null
+++ b/common/device-side/util/src/com/android/compatibility/common/util/ShellIdentityUtils.java
@@ -0,0 +1,92 @@
+/*
+ * Copyright (C) 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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);
+    }
+
+    /**
+     * 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();
+        }
+    }
+
+    /**
+     * 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..3c3e52c 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,19 +22,33 @@
 import android.app.ActivityManager;
 import android.app.ActivityManager.MemoryInfo;
 import android.app.Instrumentation;
+import android.app.UiAutomation;
 import android.content.Context;
+import android.device.loggers.TestLogData;
 import android.os.ParcelFileDescriptor;
 import android.os.StatFs;
 import android.support.test.InstrumentationRegistry;
 import android.util.Log;
 
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+
+import java.io.File;
 import java.io.FileInputStream;
+import java.io.FileOutputStream;
 import java.io.IOException;
+import java.nio.charset.StandardCharsets;
+import java.text.SimpleDateFormat;
+import java.util.Date;
+import java.util.concurrent.Callable;
 import java.util.function.Predicate;
 
 public class SystemUtil {
     private static final String TAG = "CtsSystemUtil";
 
+    public static final SimpleDateFormat sFilenameTimestampFormat =
+            new SimpleDateFormat("yyyyMMdd_HHmmss_SSS");
+
     public static long getFreeDiskSize(Context context) {
         final StatFs statFs = new StatFs(context.getFilesDir().getAbsolutePath());
         return (long)statFs.getAvailableBlocks() * statFs.getBlockSize();
@@ -118,18 +132,71 @@
     }
 
     /**
-     * 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);
+        runCommandAndDump(logtag, cmd, null, null);
+    }
+
+    /**
+     * Runs a command and print the result on logcat.
+     *
+     * <p>If {@code testLogData} is not {@code null}, it also writes the output to a file under
+     * {@code /sdcard/cts_text_dump/}, so {@code FilePullerLogCollector} can pull it.
+     *
+     * <p>Also the test apk must have the android.permission.WRITE_EXTERNAL_STORAGE permission.
+     *
+     * <p>See cts/tests/tests/syncmanager/AndroidTest.xml and
+     * cts/tests/tests/syncmanager/AndroidManifest.xml
+     * for how to set up {@code FilePullerLogCollector} and the permission.
+     */
+    public static void runCommandAndDump(@NonNull String logtag, @NonNull String cmd,
+            @Nullable TestLogData testLogData, @Nullable String comment) {
+
+        final File logDir = new File("/sdcard/cts_text_dump/");
+
+        Log.i(logtag, "Executing: " + cmd + (comment == null ? "" : " for " + comment));
+
         final String output = runShellCommand(cmd);
+
+        // First, print on logact.
         for (String line : output.split("\\n", -1)) {
             Log.i(logtag, line);
         }
+
+        if (testLogData != null) {
+            final String filenameSuffix = cmd.replaceAll("[^-_a-zA-Z0-9]", "_");
+
+            final String filename =
+                    "text_dump_"
+                            + sFilenameTimestampFormat.format(new Date(System.currentTimeMillis()))
+                            + "_" + filenameSuffix
+                            + ".txt";
+
+            final File file = new File(logDir, filename);
+
+            if (!logDir.isDirectory() && !logDir.mkdirs()) {
+                Log.e(logtag, "Unable to create directory [" + logDir.getAbsolutePath() + "]");
+            } else {
+                try (FileOutputStream st = new FileOutputStream(file)) {
+                    final String text = "Command: [" + cmd + "]\n"
+                            + "Comment: [" + comment + "]\n"
+                            + output;
+                    st.write(text.getBytes(StandardCharsets.UTF_8));
+
+                    Log.i(logtag, "Wrote output of [" + cmd + "] to " + filename
+                            + (comment == null ? "" : " for " + comment));
+                    testLogData.addTestLog(filename, file);
+
+                } catch (IOException e) {
+                    Log.e(logtag, "Failed to write output of [" + cmd + "] to " + filename);
+                }
+            }
+        }
     }
 
     /**
-     * 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 +206,43 @@
         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 Shell's permissions, where you can specify the
+     * uiAutomation used.
+     */
+    public static void runWithShellPermissionIdentity(
+            @NonNull UiAutomation automan, @NonNull ThrowingRunnable runnable) {
+        automan.adoptShellPermissionIdentity();
+        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/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..9d8d629
--- /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
+
+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..03c3c53
--- /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>
+        <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..87855e8
--- /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
+
+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..1d8f1a7
--- /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>
+        <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..a789ca4
--- /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";
+
+    // System Properties
+    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";
+    static final String PROPERTY_ENABLE_RULES_FILE = "debug.angle.enable";
+
+    // 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_PKG = "com.google.android.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 = ANGLE_PKG + "/.MainActivity";
+
+    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, "\"\"");
+        CtsAngleCommon.setProperty(device, CtsAngleCommon.PROPERTY_TEMP_RULES_FILE, "\"\"");
+        CtsAngleCommon.setProperty(device, CtsAngleCommon.PROPERTY_ENABLE_RULES_FILE, "0");
+    }
+
+    static boolean isAngleLoadable(ITestDevice device) throws Exception {
+        PackageInfo anglePkgInfo = device.getAppPackageInfo(ANGLE_PKG);
+        String propDisablePreloading = device.getProperty(PROPERTY_DISABLE_OPENGL_PRELOADING);
+        String propGfxDriver = device.getProperty(PROPERTY_GFX_DRIVER);
+
+        // Make sure ANGLE exists on the device
+        if(anglePkgInfo == null) {
+            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 activity) throws Exception {
+        // Run the ANGLE activity so it'll clear up any 'default' settings.
+        device.executeShellCommand("am start -S -W -n \"" + activity + "\"");
+    }
+
+    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..cda98e3
--- /dev/null
+++ b/hostsidetests/angle/src/android/angle/cts/CtsAngleDeveloperOptionHostTest.java
@@ -0,0 +1,458 @@
+/*
+ * Copyright (C) 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR 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_PKG);
+        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..f4f495a
--- /dev/null
+++ b/hostsidetests/angle/src/android/angle/cts/CtsAngleRulesFileTest.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.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());
+
+        // Enable checking the rules file
+        CtsAngleCommon.setProperty(getDevice(), CtsAngleCommon.PROPERTY_ENABLE_RULES_FILE, "1");
+    }
+
+    @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);
+    }
+}
\ No newline at end of file
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 aa7e4a0..bbfc62f 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..822edce 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,48 @@
     }
 
     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.runDeviceTests(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..eec0878 100644
--- a/hostsidetests/appsecurity/src/android/appsecurity/cts/CorruptApkTests.java
+++ b/hostsidetests/appsecurity/src/android/appsecurity/cts/CorruptApkTests.java
@@ -15,15 +15,11 @@
  */
 package android.appsecurity.cts;
 
-import static com.google.common.truth.Truth.assertThat;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNotNull;
 
 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.ITestDevice;
 import com.android.tradefed.result.InputStreamSource;
-import com.android.tradefed.testtype.DeviceTestCase;
-import com.android.tradefed.testtype.IBuildReceiver;
 import com.android.tradefed.util.FileUtil;
 
 import org.junit.After;
@@ -35,104 +31,81 @@
  * 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
-public class CorruptApkTests extends DeviceTestCase implements IBuildReceiver {
+@AppModeFull(reason = "the corrupt APKs were provided as-is and we cannot modify them to comply with instant mode")
+public class CorruptApkTests extends BaseAppSecurityTest {
     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;
-
-    @Override
-    public void setBuild(IBuildInfo buildInfo) {
-        mBuildInfo = buildInfo;
-    }
-
+    private final String B71360999_APK = "CtsCorruptApkTests_b71360999.apk";
+    private final String B71361168_APK = "CtsCorruptApkTests_b71361168.apk";
+    private final String B79488511_APK = "CtsCorruptApkTests_b79488511.apk";
     @Before
-    @Override
     public void setUp() throws Exception {
-        super.setUp();
-        uninstall(B71360999_PKG);
-        uninstall(B71361168_PKG);
-        uninstall(B79488511_PKG);
+        getDevice().uninstallPackage(B71360999_PKG);
+        getDevice().uninstallPackage(B71361168_PKG);
+        getDevice().uninstallPackage(B79488511_PKG);
     }
 
     @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);
-        }
+        getDevice().uninstallPackage(B71360999_PKG);
+        getDevice().uninstallPackage(B71361168_PKG);
+        getDevice().uninstallPackage(B79488511_PKG);
     }
 
     /**
      * Tests that apks described in b/71360999 do not install successfully.
      */
     public void testFailToInstallCorruptStringPoolHeader_b71360999() throws Exception {
-        final String APK_PATH = "CtsCorruptApkTests_b71360999.apk";
-        assertInstallNoFatalError(APK_PATH, B71360999_PKG);
+        if (getDevice().getApiLevel() < 28) {
+            return;
+        }
+        assertInstallWithoutFatalError(B71360999_APK, B71360999_PKG);
     }
 
     /**
      * 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);
+        if (getDevice().getApiLevel() < 28) {
+            return;
+        }
+        assertInstallWithoutFatalError(B71361168_APK, 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);
+        if (getDevice().getApiLevel() < 28) {
+            return;
+        }
+        assertInstallWithoutFatalError(B79488511_APK, 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();
+     * Asserts that installing the application does not cause a native error [typically
+     * the result of a buffer overflow or an out-of-bounds read].
+     */
+    private void assertInstallWithoutFatalError(String apk, String pkg) throws Exception {
+        getDevice().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);
-        }
+        new InstallMultiple().addApk(apk).runExpectingFailure();
 
         // 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);
+        final File tmpTxtFile = FileUtil.createTempFile("logcat", ".txt");
+        final InputStreamSource source = getDevice().getLogcat(200 * 1024);
         try {
             assertNotNull(source);
-            tmpTxtFile = FileUtil.createTempFile("logcat", ".txt");
             FileUtil.writeToFile(source.createInputStream(), tmpTxtFile);
-            String s = FileUtil.readStringFromFile(tmpTxtFile);
+            final String s = FileUtil.readStringFromFile(tmpTxtFile);
             assertFalse(s.contains("SIGSEGV"));
             assertFalse(s.contains("==ERROR"));
         } finally {
             source.close();
-            if (tmpTxtFile != null) {
-                FileUtil.deleteFile(tmpTxtFile);
-            }
+            FileUtil.deleteFile(tmpTxtFile);
         }
     }
 }
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..d4b516c 100644
--- a/hostsidetests/appsecurity/src/android/appsecurity/cts/DocumentsTest.java
+++ b/hostsidetests/appsecurity/src/android/appsecurity/cts/DocumentsTest.java
@@ -82,6 +82,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 56d605e..e030403 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,341 @@
     }
 
     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.runDeviceTests(getDevice(), NORMAL_PKG, TEST_CLASS, "testQuery");
     }
 
+    @Test
     public void testNormalStartNormal() throws Exception {
-        if (isDeviceUnsupported()) {
+        if (mIsUnsupportedDevice) {
             return;
         }
-        runDeviceTests(NORMAL_PKG, TEST_CLASS, "testStartNormal");
+        Utils.runDeviceTests(getDevice(), NORMAL_PKG, TEST_CLASS, "testStartNormal");
     }
 
+    @Test
     public void testNormalStartEphemeral() throws Exception {
-        if (isDeviceUnsupported()) {
+        if (mIsUnsupportedDevice) {
             return;
         }
-        runDeviceTests(NORMAL_PKG, TEST_CLASS, "testStartEphemeral");
+        Utils.runDeviceTests(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.runDeviceTests(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.runDeviceTests(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.runDeviceTests(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.runDeviceTests(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.runDeviceTests(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.runDeviceTests(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.runDeviceTests(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.runDeviceTests(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.runDeviceTests(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.runDeviceTests(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.runDeviceTests(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.runDeviceTests(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.runDeviceTests(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.runDeviceTests(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.runDeviceTests(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.runDeviceTests(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.runDeviceTests(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.runDeviceTests(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.runDeviceTests(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.runDeviceTests(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.runDeviceTests(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.runDeviceTests(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.runDeviceTests(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.runDeviceTests(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.runDeviceTests(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.runDeviceTests(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.runDeviceTests(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.runDeviceTests(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.runDeviceTests(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.runDeviceTests(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.runDeviceTests(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.runDeviceTests(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.runDeviceTests(getDevice(), UPGRADED_PKG, TEST_CLASS,
+                "testInstantApplicationWritePreferences");
+        Utils.runDeviceTests(getDevice(), UPGRADED_PKG, TEST_CLASS,
+                "testInstantApplicationWriteFile");
         installFullApp(UPGRADED_APK);
-        runDeviceTests(UPGRADED_PKG, TEST_CLASS, "testFullApplicationReadPreferences");
-        runDeviceTests(UPGRADED_PKG, TEST_CLASS, "testFullApplicationReadFile");
+        Utils.runDeviceTests(getDevice(), UPGRADED_PKG, TEST_CLASS,
+                "testFullApplicationReadPreferences");
+        Utils.runDeviceTests(getDevice(), UPGRADED_PKG, TEST_CLASS, "testFullApplicationReadFile");
     }
 
     private static final HashMap<String, String> makeArgs(
@@ -445,7 +478,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,57 +504,54 @@
         return dump.toString();
     }
 
-    private void runDeviceTests(String packageName, String testClassName, String testMethodName)
-            throws DeviceNotAvailableException {
-        Utils.runDeviceTests(getDevice(), packageName, testClassName, testMethodName);
-    }
-
-    private void runDeviceTests(String packageName, String testClassName, String testMethodName,
-            Map<String, String> testArgs) throws DeviceNotAvailableException {
-        Utils.runDeviceTests(getDevice(), packageName, testClassName, testMethodName, 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..d5e37d2 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,88 @@
         }
     }
 
+    @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);
+            }
+        } 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 +348,9 @@
      */
     @Test
     public void testMultiViewMoveConsistency() throws Exception {
+        // TODO: remove this test once isolated storage is always enabled
+        Assume.assumeFalse(hasIsolatedStorage());
+
         try {
             wipePrimaryExternalStorage();
 
@@ -281,7 +405,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 +456,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 persist.sys.isolated_storage")
+                .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..0b5594b 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,10 +29,13 @@
 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 runDeviceTests(ITestDevice device, String packageName, String testClassName,
@@ -156,4 +162,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..428fbde 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
@@ -80,16 +80,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 {
@@ -339,16 +339,18 @@
         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());
     }
 
@@ -543,6 +545,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 +636,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/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/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..e5fc238b 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.
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..70a6aa9 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
@@ -22,6 +22,7 @@
 import static com.android.cts.externalstorageapp.CommonExternalStorageTest.assertFileNoAccess;
 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 +51,15 @@
             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));
+        }
+    }
 }
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..cc70445
--- /dev/null
+++ b/hostsidetests/appsecurity/test-apps/ListeningPortsApp/src/android/appsecurity/cts/listeningports/ListeningPortsTest.java
@@ -0,0 +1,275 @@
+/*
+ * 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
+        //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
+    }
+
+    /**
+     * 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..1c2427a 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
@@ -16,21 +16,26 @@
 
 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.assertEquals;
 import static junit.framework.Assert.assertTrue;
 
 import android.Manifest;
+import android.content.ContentValues;
 import android.content.Context;
 import android.content.pm.PackageManager;
+import android.database.Cursor;
+import android.net.Uri;
 import android.os.Environment;
 import android.os.Process;
+import android.provider.CalendarContract;
+
 import org.junit.Test;
 
 import java.io.File;
@@ -42,26 +47,27 @@
 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 is granted by default
         assertEquals(PackageManager.PERMISSION_GRANTED,
-                context.checkPermission(android.Manifest.permission.READ_EXTERNAL_STORAGE,
+                mContext.checkPermission(android.Manifest.permission.READ_EXTERNAL_STORAGE,
                         Process.myPid(), Process.myUid()));
         assertEquals(PackageManager.PERMISSION_GRANTED,
-                context.checkPermission(android.Manifest.permission.WRITE_EXTERNAL_STORAGE,
+                mContext.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)) {
+        for (File path : getAllPackageSpecificPaths(mContext)) {
             if (path != null) {
                 assertDirReadWriteAccess(path);
             }
         }
-        assertMediaReadWriteAccess(getInstrumentation().getContext().getContentResolver());
+        assertMediaReadWriteAccess(mContext.getContentResolver());
     }
 
     @Test
@@ -70,81 +76,89 @@
         revokePermissions(new String[]{android.Manifest.permission.READ_EXTERNAL_STORAGE}, true);
     }
 
+    private void assertNoStorageAccess() throws Exception {
+        assertEquals(Environment.MEDIA_UNMOUNTED, Environment.getExternalStorageState());
+
+        assertDirNoAccess(Environment.getExternalStorageDirectory());
+        for (File dir : getAllPackageSpecificPaths(mContext)) {
+            if (dir != null) {
+                assertDirNoAccess(dir);
+            }
+        }
+        assertMediaNoAccess(mContext.getContentResolver(), true);
+
+        // Just to be sure, poke explicit path
+        assertDirNoAccess(new File(Environment.getExternalStorageDirectory(),
+                "/Android/data/" + mContext.getPackageName()));
+    }
+
     @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
         assertEquals(PackageManager.PERMISSION_GRANTED,
-                context.checkPermission(android.Manifest.permission.READ_EXTERNAL_STORAGE,
+                mContext.checkPermission(android.Manifest.permission.READ_EXTERNAL_STORAGE,
                         Process.myPid(), Process.myUid()));
         assertEquals(PackageManager.PERMISSION_GRANTED,
-                context.checkPermission(android.Manifest.permission.WRITE_EXTERNAL_STORAGE,
+                mContext.checkPermission(android.Manifest.permission.WRITE_EXTERNAL_STORAGE,
                         Process.myPid(), Process.myUid()));
-        assertEquals(Environment.MEDIA_UNMOUNTED, Environment.getExternalStorageState());
 
-        assertDirNoAccess(Environment.getExternalStorageDirectory());
-        for (File dir : getAllPackageSpecificPaths(context)) {
-            if (dir != null) {
-                assertDirNoAccess(dir);
-            }
-        }
-        assertMediaNoAccess(getInstrumentation().getContext().getContentResolver(), true);
-
-        // Just to be sure, poke explicit path
-        assertDirNoAccess(new File(Environment.getExternalStorageDirectory(),
-                "/Android/data/" + getInstrumentation().getContext().getPackageName()));
+        assertNoStorageAccess();
     }
 
     @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 +179,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..283be89 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
@@ -253,7 +253,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) {
@@ -287,27 +287,43 @@
         return result;
     }
 
+    protected void selectForegroundOnlyOption() {
+        getUiDevice().findObject(By.res(
+                "com.android.permissioncontroller:id/foreground_only_radio_button")).click();
+    }
+
+    protected void selectAlwaysOption() {
+        getUiDevice().findObject(By.res(
+                "com.android.permissioncontroller:id/always_radio_button")).click();
+    }
+
+    protected void selectDenyAndDontAskAgainOption() {
+        getUiDevice().findObject(By.res(
+                "com.android.permissioncontroller:id/deny_dont_ask_again_radio_button")).click();
+    }
+
     protected void clickAllowButton() throws Exception {
         scrollToBottomIfWatch();
         getUiDevice().findObject(new UiSelector().resourceId(
-                "com.android.packageinstaller:id/permission_allow_button")).click();
+                "com.android.permissioncontroller:id/permission_allow_button")).click();
     }
 
     protected void clickDenyButton() throws Exception {
         scrollToBottomIfWatch();
         getUiDevice().findObject(new UiSelector().resourceId(
-                "com.android.packageinstaller:id/permission_deny_button")).click();
+                "com.android.permissioncontroller:id/permission_deny_button")).click();
     }
 
     protected void clickDontAskAgainCheckbox() throws Exception {
         getUiDevice().findObject(new UiSelector().resourceId(
-                "com.android.packageinstaller:id/do_not_ask_checkbox")).click();
+                "com.android.permissioncontroller:id/do_not_ask_checkbox")).click();
     }
 
     protected void clickDontAskAgainButton() throws Exception {
         scrollToBottomIfWatch();
         getUiDevice().findObject(new UiSelector().resourceId(
-                "com.android.packageinstaller:id/permission_deny_dont_ask_again_button")).click();
+                "com.android.permissioncontroller:id/permission_deny_dont_ask_again_button"))
+                .click();
     }
 
     protected void grantPermission(String permission) throws Exception {
@@ -373,7 +389,7 @@
         waitForIdle();
 
         for (String permission : permissions) {
-            // Find the permission toggle
+            // Find the permission screen
             String permissionLabel = getPermissionLabel(permission);
 
             AccessibilityNodeInfo labelView = getNodeTimed(() -> findByText(permissionLabel), true);
@@ -382,17 +398,28 @@
             AccessibilityNodeInfo itemView = findCollectionItem(labelView);
             Assert.assertNotNull("Permission item should be present", itemView);
 
-            final AccessibilityNodeInfo toggleView = findSwitch(itemView);
-            Assert.assertNotNull("Permission toggle should be present", toggleView);
+            click(itemView);
 
-            final boolean wasGranted = toggleView.isChecked();
+            String denyLabel = mContext.getResources().getString(R.string.Deny);
+            AccessibilityNodeInfo denyView = getNodeTimed(() -> findByText(denyLabel), true);
+            Assert.assertNotNull("Deny label should be present", denyView);
+
+            final boolean wasGranted = !denyView.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);
+                    AccessibilityNodeInfo allowView = getNodeTimed(() -> findByText(allowLabel), false);
+                    if (allowView == null) {
+                        String allowAllLabel = mContext.getResources().getString(R.string.AllowAll);
+                        allowView = getNodeTimed(() -> findByText(allowAllLabel), true);
+                    }
+                    Assert.assertNotNull("Allow label should be present", allowView);
+
+                    click(allowView);
                 } else {
-                    click(itemView);
+                    click(denyView);
                 }
 
                 waitForIdle();
@@ -401,7 +428,7 @@
                     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();
@@ -414,6 +441,9 @@
                     waitForIdle();
                 }
             }
+
+            getUiDevice().pressBack();
+            waitForIdle();
         }
 
         getUiDevice().pressBack();
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..ebee589 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
@@ -29,6 +29,7 @@
 import android.Manifest;
 import android.content.pm.PackageManager;
 import android.os.Environment;
+import android.support.test.InstrumentationRegistry;
 
 import org.junit.Before;
 import org.junit.Test;
@@ -497,9 +498,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 +659,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);
     }
 
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..f568989
--- /dev/null
+++ b/hostsidetests/appsecurity/test-apps/UsePermissionApp28/src/com/android/cts/usepermission/UsePermissionTest28.java
@@ -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 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 {
+                        selectAlwaysOption();
+                        clickAllowButton();
+                    } 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..fdfcfa2
--- /dev/null
+++ b/hostsidetests/appsecurity/test-apps/UsePermissionAppP0/src/com/android/cts/usepermission/UsePermissionTestP0.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 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::selectAlwaysOption, this::clickAllowButton);
+        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::selectForegroundOnlyOption, this::clickAllowButton);
+        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::selectDenyAndDontAskAgainOption,
+                this::clickDenyButton);
+        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..3ab9eb9
--- /dev/null
+++ b/hostsidetests/atrace/src/android/atrace/cts/AtraceHostTestBase.java
@@ -0,0 +1,246 @@
+/*
+ * Copyright (C) 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR 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 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());
+        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/AndroidTest.xml b/hostsidetests/backup/AndroidTest.xml
index 868e20f..46a17be 100644
--- a/hostsidetests/backup/AndroidTest.xml
+++ b/hostsidetests/backup/AndroidTest.xml
@@ -16,6 +16,8 @@
 <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" />
     <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/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..c5241b7 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");
     }
+
+    private 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/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/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/compilation/AndroidTest.xml b/hostsidetests/compilation/AndroidTest.xml
index fe17ac8..9383a79 100644
--- a/hostsidetests/compilation/AndroidTest.xml
+++ b/hostsidetests/compilation/AndroidTest.xml
@@ -16,6 +16,7 @@
 <configuration description="Config for CTS Compilation Test">
     <option name="test-suite-tag" value="cts" />
     <option name="config-descriptor:metadata" key="component" value="art" />
+    <target_preparer class="com.android.tradefed.targetprep.RootTargetPreparer" />
     <test class="com.android.compatibility.common.tradefed.testtype.JarHostTest" >
         <option name="jar" value="CtsCompilationTestCases.jar" />
         <option name="runtime-hint" value="9m45s" />
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..f745781 100644
--- a/hostsidetests/devicepolicy/AndroidTest.xml
+++ b/hostsidetests/devicepolicy/AndroidTest.xml
@@ -16,6 +16,9 @@
 <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" />
 
     <!-- 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..b114f96 100644
--- a/hostsidetests/devicepolicy/app/CertInstaller/AndroidManifest.xml
+++ b/hostsidetests/devicepolicy/app/CertInstaller/AndroidManifest.xml
@@ -20,6 +20,8 @@
     <uses-sdk android:minSdkVersion="22"/>
 
     <application>
+        <uses-library android:name="android.test.runner" />
+
         <receiver android:name=".CertInstallerReceiver">
             <intent-filter>
                 <action android:name="com.android.cts.certinstaller.install_cert" />
@@ -31,4 +33,13 @@
         </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/DelegatedDeviceIdAttestationTest.java b/hostsidetests/devicepolicy/app/CertInstaller/src/com/android/cts/certinstaller/DelegatedDeviceIdAttestationTest.java
new file mode 100644
index 0000000..fffee81
--- /dev/null
+++ b/hostsidetests/devicepolicy/app/CertInstaller/src/com/android/cts/certinstaller/DelegatedDeviceIdAttestationTest.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.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() {
+        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..ffa93b9
--- /dev/null
+++ b/hostsidetests/devicepolicy/app/CertInstaller/src/com/android/cts/certinstaller/DirectDelegatedCertInstallerTest.java
@@ -0,0 +1,232 @@
+/*
+ * Copyright (C) 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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.security.AttestedKeyPair;
+import android.security.KeyChain;
+import android.security.KeyChainException;
+import android.security.keystore.KeyGenParameterSpec;
+import android.security.keystore.KeyProperties;
+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();
+        }
+    }
+
+    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..03df393 100644
--- a/hostsidetests/devicepolicy/app/DelegateApp/Android.mk
+++ b/hostsidetests/devicepolicy/app/DelegateApp/Android.mk
@@ -29,7 +29,8 @@
 LOCAL_STATIC_JAVA_LIBRARIES = \
     androidx.legacy_legacy-support-v4 \
     ctstestrunner \
-    android-support-test
+    android-support-test \
+    truth-prebuilt
 
 LOCAL_SDK_VERSION := current
 
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/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/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..90f24cb 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,7 +36,8 @@
 
         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() {
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..3a123d9
--- /dev/null
+++ b/hostsidetests/devicepolicy/app/DeviceAdmin/src/com.android.cts.deviceadmin/DeviceAdminWithEnterprisePoliciesBlockedTest.java
@@ -0,0 +1,116 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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() {
+        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() {
+        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..0fc8f48 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,6 +16,10 @@
 
 package com.android.cts.deviceandprofileowner;
 
+import static android.app.admin.DevicePolicyManager.DELEGATION_CERT_INSTALL;
+
+import static com.google.common.truth.Truth.assertThat;
+
 import android.app.KeyguardManager;
 import android.app.admin.DevicePolicyManager;
 import android.content.BroadcastReceiver;
@@ -25,7 +29,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 +38,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 +72,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:
      *
@@ -275,6 +280,41 @@
         }
     }
 
+    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();
+    }
+
     /**
      * installKeyPair() requires the system to have a lockscreen password, which should have been
      * set by the host side test.
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..196000f
--- /dev/null
+++ b/hostsidetests/devicepolicy/app/DeviceAndProfileOwner/src/com/android/cts/deviceandprofileowner/DeviceIdAttestationTest.java
@@ -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.
+ */
+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() {
+        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..86136b0
--- /dev/null
+++ b/hostsidetests/devicepolicy/app/DeviceAndProfileOwner/src/com/android/cts/deviceandprofileowner/DevicePolicyLoggingTest.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 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.provider.Settings.Secure.DEFAULT_INPUT_METHOD;
+import static android.provider.Settings.Secure.SKIP_FIRST_USE_HINTS;
+
+import android.app.admin.DevicePolicyManager;
+import android.content.ContentResolver;
+import android.os.UserManager;
+import android.provider.Settings;
+
+/**
+ * Tests for DevicePolicyEvent metrics logged in {@link DevicePolicyManager}.
+ */
+public class DevicePolicyLoggingTest extends BaseDeviceAdminTest {
+
+    public static final String PACKAGE_NAME = "com.android.cts.permissionapp";
+
+    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);
+    }
+}
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..20082ef 100644
--- a/hostsidetests/devicepolicy/app/DeviceOwner/AndroidManifest.xml
+++ b/hostsidetests/devicepolicy/app/DeviceOwner/AndroidManifest.xml
@@ -29,6 +29,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/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/InstallUpdateTest.java b/hostsidetests/devicepolicy/app/DeviceOwner/src/com/android/cts/deviceowner/InstallUpdateTest.java
new file mode 100644
index 0000000..6d38843
--- /dev/null
+++ b/hostsidetests/devicepolicy/app/DeviceOwner/src/com/android/cts/deviceowner/InstallUpdateTest.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 com.android.cts.deviceowner;
+
+import android.app.admin.DevicePolicyManager;
+import android.net.Uri;
+
+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 {
+
+    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);
+    }
+
+    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));
+    }
+}
\ No newline at end of file
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..5118b83 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;
@@ -399,9 +400,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) {
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 4fa6235..cc7c29a 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
@@ -16,6 +16,8 @@
 package com.android.cts.deviceowner;
 
 
+import static com.google.common.truth.Truth.assertThat;
+
 import android.app.admin.DevicePolicyManager;
 import android.app.admin.FreezePeriod;
 import android.app.admin.SystemUpdatePolicy;
@@ -25,14 +27,16 @@
 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.Pair;
 
+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.Semaphore;
 import java.util.concurrent.TimeUnit;
 
@@ -235,6 +239,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..5404058 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,49 @@
         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) {
+            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 testNoSystemAppHasSyntheticAppDetailsActivityInjected() throws Exception {
+        List<LauncherActivityInfo> activities = mLauncherApps.getActivityList(null, mUser);
+        for (LauncherActivityInfo activity : activities) {
+            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..e59a305 100644
--- a/hostsidetests/devicepolicy/app/ManagedProfile/Android.mk
+++ b/hostsidetests/devicepolicy/app/ManagedProfile/Android.mk
@@ -33,12 +33,14 @@
 	compatibility-device-util \
 	ub-uiautomator \
 	android-support-test \
-	guava
+	guava \
+	truth-prebuilt
 
 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..176f25d 100644
--- a/hostsidetests/devicepolicy/app/ManagedProfile/AndroidManifest.xml
+++ b/hostsidetests/devicepolicy/app/ManagedProfile/AndroidManifest.xml
@@ -17,7 +17,7 @@
 <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.CHANGE_WIFI_STATE" />
     <uses-permission android:name="android.permission.AUTHENTICATE_ACCOUNTS" />
@@ -31,6 +31,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">
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/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..3d28422
--- /dev/null
+++ b/hostsidetests/devicepolicy/app/ManagedProfile/src/com/android/cts/managedprofile/CrossProfileCalendarTest.java
@@ -0,0 +1,389 @@
+/*
+ * Copyright (C) 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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 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.database.Cursor;
+import android.net.Uri;
+import android.provider.CalendarContract;
+import android.provider.Settings.Secure;
+import android.test.AndroidTestCase;
+import android.text.TextUtils;
+import android.text.format.DateUtils;
+import android.text.format.Time;
+
+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 long WORK_EVENT_DTSTART = parseTimeStringToMillis(
+            "2018-05-01T00:00:00", WORK_TIMEZONE);
+    private static long WORK_EVENT_DTEND = parseTimeStringToMillis(
+            "2018-05-01T20:00:00", WORK_TIMEZONE);
+    private final long WORK_EVENT_DTSTART_2 = parseTimeStringToMillis(
+            "2013-05-01T00:00:00", WORK_TIMEZONE);
+    private final long WORK_EVENT_DTEND_2 = parseTimeStringToMillis(
+            "2013-05-01T20:00:00", 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 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 );
+    }
+
+    @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.addCrossProfileCalendarPackage(
+                ADMIN_RECEIVER_COMPONENT, MANAGED_PROFILE_PKG);
+        whitelist = mDevicePolicyManager.getCrossProfileCalendarPackages(
+                ADMIN_RECEIVER_COMPONENT);
+        assertThat(whitelist.size()).isEqualTo(1);
+        assertThat(whitelist.contains(MANAGED_PROFILE_PKG)).isTrue();
+
+        assertThat(mDevicePolicyManager.removeCrossProfileCalendarPackage(
+                ADMIN_RECEIVER_COMPONENT, MANAGED_PROFILE_PKG)).isTrue();
+        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_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);
+    }
+
+    // Utils method, not a actual test. Ran from ManagedProfileTest.java to set up for actual tests.
+    public void testWhitelistManagedProfilePackage() {
+        requireRunningOnManagedProfile();
+        mDevicePolicyManager.addCrossProfileCalendarPackage(
+                ADMIN_RECEIVER_COMPONENT, MANAGED_PROFILE_PKG);
+    }
+
+    // Utils method, not a actual test. Ran from ManagedProfileTest.java to set up for actual tests.
+    public void testRemoveManagedProfilePackageFromWhitelist() {
+        requireRunningOnManagedProfile();
+        assertThat(mDevicePolicyManager.removeCrossProfileCalendarPackage(
+                ADMIN_RECEIVER_COMPONENT, MANAGED_PROFILE_PKG)).isTrue();
+    }
+
+    // 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 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/DevicePolicyManagerParentSupportTest.java b/hostsidetests/devicepolicy/app/ManagedProfile/src/com/android/cts/managedprofile/DevicePolicyManagerParentSupportTest.java
new file mode 100644
index 0000000..9bd721f
--- /dev/null
+++ b/hostsidetests/devicepolicy/app/ManagedProfile/src/com/android/cts/managedprofile/DevicePolicyManagerParentSupportTest.java
@@ -0,0 +1,151 @@
+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() {
+        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() {
+        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() {
+        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() {
+        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() {
+        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() {
+        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/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/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..3f44008 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(0, null);
         }
     }
 }
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/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/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/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..5d255e0e 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,14 @@
 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.SharedPreferences;
+import android.os.IBinder;
 import android.os.PersistableBundle;
 import android.support.test.InstrumentationRegistry;
 import android.support.test.filters.SmallTest;
@@ -44,12 +47,27 @@
         }
     }
 
+    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;
@@ -97,6 +115,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/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/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/BaseDevicePolicyTest.java b/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/BaseDevicePolicyTest.java
index ef51156..5ec9ef8 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 {
@@ -122,6 +136,8 @@
     /** 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;
@@ -155,6 +171,7 @@
         removeTestUsers();
         // Unlock keyguard before test
         wakeupAndDismissKeyguard();
+        stayAwake();
         // Go to home.
         executeShellCommand("input keyevent KEYCODE_HOME");
     }
@@ -324,9 +341,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 +457,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 +739,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 +886,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 +934,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..997b831 100644
--- a/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/DeviceAndProfileOwnerHostSideTransferTest.java
+++ b/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/DeviceAndProfileOwnerHostSideTransferTest.java
@@ -3,6 +3,7 @@
 import com.android.tradefed.device.DeviceNotAvailableException;
 
 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 +15,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;
@@ -191,9 +196,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 fbe75d2..fd5e2e7 100644
--- a/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/DeviceAndProfileOwnerTest.java
+++ b/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/DeviceAndProfileOwnerTest.java
@@ -16,12 +16,17 @@
 
 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 +35,8 @@
 import java.util.List;
 import java.util.Map;
 
+import android.stats.devicepolicy.EventId;
+
 /**
  * Set of tests for use cases that apply to profile and device owner.
  * This class is the base class of MixedProfileOwnerTest, MixedDeviceOwnerTest and
@@ -45,6 +52,9 @@
     protected 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";
 
@@ -129,6 +139,24 @@
     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;
+
     // 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 +196,31 @@
         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());
+    }
+
+    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;
@@ -609,6 +662,44 @@
         }
     }
 
+    public interface DelegatedCertInstallerTestAction {
+        void run() throws Exception;
+    }
+
+    protected void setUpDelegatedCertInstallerAndRunTests(DelegatedCertInstallerTestAction test)
+            throws Exception {
+        installAppAsUser(CERT_INSTALLER_APK, 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, ".DelegatedCertInstallerHelper",
+                    "testManualSetCertInstallerDelegate", mUserId);
+
+            test.run();
+        } finally {
+            runDeviceTestsAsUser(DEVICE_ADMIN_PKG, ".DelegatedCertInstallerHelper",
+                    "testManualClearCertInstallerDelegate", mUserId);
+
+            changeUserCredential(null, "1234", 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 {
@@ -679,6 +770,9 @@
         }
         // 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";
@@ -702,6 +796,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);
@@ -844,6 +947,64 @@
         executeDeviceTestClass(".PasswordSufficientInitiallyTest");
     }
 
+    public void testGetCurrentFailedPasswordAttempts() throws Exception {
+        if (!mHasFeature) {
+            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) {
+            return;
+        }
+        executeDeviceTestClass(".PasswordExpirationTest");
+    }
+
+    public void testGetPasswordExpiration() throws Exception {
+        if (!mHasFeature) {
+            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;
@@ -915,6 +1076,250 @@
         }
     }
 
+    public void testInstallKeyPairLogged() throws Exception {
+        if (!mHasFeature) {
+            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);
+            assertMetricsLogged(getDevice(), () -> {
+                executeDeviceTestMethod(".KeyManagementTest", "testCanInstallCertChain");
+            }, new DevicePolicyEventWrapper.Builder(EventId.INSTALL_KEY_PAIR_VALUE)
+                    .setAdminPackageName(DEVICE_ADMIN_PKG)
+                    .setBoolean(false)
+                    .build());
+
+        } finally {
+            changeUserCredential(null, "1234", mUserId);
+        }
+    }
+
+    public void testPermittedAccessibilityServices() throws Exception {
+        if (!mHasFeature) {
+            return;
+        }
+
+        executeDeviceTestClass(".AccessibilityServicesTest");
+    }
+
+    public void testPermittedInputMethods() throws Exception {
+        if (!mHasFeature) {
+            return;
+        }
+
+        executeDeviceTestClass(".InputMethodsTest");
+    }
+
+    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());
+    }
+
     /**
      * Executes a test class on device. Prior to running, turn off background data usage
      * restrictions, and restore the original restrictions after the test.
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..d349d1d 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,11 @@
     private static final int SECURITY_EVENTS_BATCH_SIZE = 100;
 
     private static final String ARG_NETWORK_LOGGING_BATCH_COUNT = "batchCount";
-
-    private final List<String> NO_SETUP_WIZARD_PROVISIONING_MODE =
-            Arrays.asList("DISABLED", "EMULATOR");
+    private static final String TEST_UPDATE_LOCATION = "/data/local/tmp/cts/deviceowner";
 
     /** 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 +86,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 +101,7 @@
             getDevice().uninstallPackage(DEVICE_OWNER_PKG);
             switchUser(USER_SYSTEM);
             removeTestUsers();
+            getDevice().executeShellCommand(" rm -r " + TEST_UPDATE_LOCATION);
         }
 
         super.tearDown();
@@ -341,19 +345,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 +441,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++) {
@@ -761,6 +774,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;
@@ -855,12 +891,60 @@
     }
 
     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");
+    }
+
+    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 testRetrieveSecurityLogsLogged() throws Exception {
+        if (!mHasFeature) {
+            return;
+        }
+        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());
+    }
+
     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/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..58e36ac 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;
@@ -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) {
@@ -170,12 +175,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,8 +229,8 @@
 
     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);
     }
 
@@ -297,10 +336,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);
         }
     }
 
@@ -372,10 +413,10 @@
                 MANAGED_PROFILE_PKG + ".ManagedProfileTest", mProfileUserId);
 
         // 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 +564,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 +642,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 +690,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 +749,7 @@
     public void testBluetooth() throws Exception {
         boolean hasBluetooth = hasDeviceFeature(FEATURE_BLUETOOTH);
         if (!mHasFeature || !hasBluetooth) {
-            return ;
+            return;
         }
 
         runDeviceTestsAsUser(MANAGED_PROFILE_PKG, ".BluetoothTest",
@@ -714,39 +768,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,
@@ -836,6 +863,14 @@
                 mProfileUserId);
     }
 
+    public void testDevicePolicyManagerParentSupport() throws Exception {
+        if (!mHasFeature) {
+            return;
+        }
+        runDeviceTestsAsUser(
+                MANAGED_PROFILE_PKG, ".DevicePolicyManagerParentSupportTest", mProfileUserId);
+    }
+
     public void testBluetoothContactSharingDisabled() throws Exception {
         if (!mHasFeature) {
             return;
@@ -965,7 +1000,7 @@
                 + getDevice().executeShellCommand(command));
     }
 
-    public void testPhoneAccountVisibility() throws Exception  {
+    public void testPhoneAccountVisibility() throws Exception {
         if (!mHasFeature) {
             return;
         }
@@ -1013,7 +1048,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 +1083,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 {
@@ -1153,6 +1188,30 @@
                 "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) {
             return;
@@ -1206,7 +1265,7 @@
             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 +1273,197 @@
         verifyUnifiedPassword(false);
     }
 
+    public void testUnlockWorkProfile_deviceWidePassword() throws Exception {
+        if (!mHasFeature || !mSupportsFbe) {
+            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) {
+            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) {
+            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;
+        }
+        runDeviceTestsAsUser(MANAGED_PROFILE_PKG, ".CrossProfileCalendarTest",
+                "testCrossProfileCalendarPackage", mProfileUserId);
+    }
+
+    public void testCrossProfileCalendar() throws Exception {
+        if (!mHasFeature) {
+            return;
+        }
+        runCrossProfileCalendarTestsWhenWhitelistedAndEnabled();
+        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_canAccessWorkInstancesSearch1", mParentUserId);
+            runDeviceTestsAsUser(MANAGED_PROFILE_PKG, ".CrossProfileCalendarTest",
+                    "testPrimaryProfile_canAccessWorkInstancesSearch2", mParentUserId);
+
+        } finally {
+            // Cleanup.
+            runDeviceTestsAsUser(MANAGED_PROFILE_PKG, ".CrossProfileCalendarTest",
+                    "testRemoveManagedProfilePackageFromWhitelist", 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",
+                    "testRemoveManagedProfilePackageFromWhitelist", 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);
+        } finally {
+            // Cleanup.
+            runDeviceTestsAsUser(MANAGED_PROFILE_PKG, ".CrossProfileCalendarTest",
+                    "testCleanupTestCalendarDataForWorkProfile", mProfileUserId);
+            runDeviceTestsAsUser(MANAGED_PROFILE_PKG, ".CrossProfileCalendarTest",
+                    "testDisableCrossProfileCalendarSettings", mProfileUserId);
+        }
+    }
+
+    public void testCreateSeparateChallengeChangedLogged() throws Exception {
+        if (!mHasFeature) {
+            return;
+        }
+        assertMetricsLogged(getDevice(), () -> {
+            changeUserCredential(
+                    "1234" /* newCredential */, null /* oldCredential */, mProfileUserId);
+        }, new DevicePolicyEventWrapper.Builder(EventId.SEPARATE_PROFILE_CHALLENGE_CHANGED_VALUE)
+                .setBoolean(true)
+                .build());
+    }
+
     private void verifyUnifiedPassword(boolean unified) throws DeviceNotAvailableException {
         final String testMethod =
                 unified ? "testUsingUnifiedPassword" : "testNotUsingUnifiedPassword";
@@ -1274,7 +1524,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 +1618,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..829fad2 100644
--- a/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/MixedDeviceOwnerTest.java
+++ b/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/MixedDeviceOwnerTest.java
@@ -16,14 +16,6 @@
 
 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;
-
 /**
  * Set of tests for device owner use cases that also apply to profile owners.
  * Tests that should be run identically in both cases are added in DeviceAndProfileOwnerTest.
@@ -57,5 +49,14 @@
         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));
+    }
 }
diff --git a/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/MixedManagedProfileOwnerTest.java b/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/MixedManagedProfileOwnerTest.java
index b39e1ad..31d03fd 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;
@@ -170,4 +172,59 @@
     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;
+        }
+
+        try {
+            // Set a non-empty device lockscreen password, which is a precondition for interacting
+            // with KeyStore.
+            changeUserCredential("1234", null, mUserId);
+            // 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);
+        } finally {
+            changeUserCredential(null, "1234", mUserId);
+        }
+    }
+
 }
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..9be917e
--- /dev/null
+++ b/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/PasswordComplexityTest.java
@@ -0,0 +1,32 @@
+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 (!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 {
+        getDevice().uninstallPackage(PKG);
+
+        super.tearDown();
+    }
+
+    public void testGetPasswordComplexity() throws Exception {
+        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..befb47c 100644
--- a/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/ProfileOwnerTest.java
+++ b/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/ProfileOwnerTest.java
@@ -92,4 +92,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/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/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/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/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/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/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/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/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/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/hostside/AndroidTest.xml b/hostsidetests/inputmethodservice/hostside/AndroidTest.xml
index 1575659..9e5ae1d 100644
--- a/hostsidetests/inputmethodservice/hostside/AndroidTest.xml
+++ b/hostsidetests/inputmethodservice/hostside/AndroidTest.xml
@@ -18,6 +18,7 @@
 <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" />
     <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" />
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..ec5a877 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.
  */
@@ -744,6 +744,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/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/security/src/android/cts/security/SELinuxHostTest.java b/hostsidetests/security/src/android/cts/security/SELinuxHostTest.java
index cc423db..1d0c302 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 1985a4d..0dce091 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>
     <!-- Support for 64-bit software codecs has been deprecated from o-mr1-sts-release    -->
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..5b40654
--- /dev/null
+++ b/hostsidetests/securitybulletin/securityPatch/Bug-115739809/poc.cpp
@@ -0,0 +1,210 @@
+/**
+* Copyright (C) 2018 The Android Open Source Project
+*
+* Licensed under the Apache License, Version 2.0 (the "License");
+* you may not use this file except in compliance with the License.
+* You may obtain a copy of the License at
+*
+*      http://www.apache.org/licenses/LICENSE-2.0
+*
+* Unless required by applicable law or agreed to in writing, software
+* distributed under the License is distributed on an "AS IS" BASIS,
+* WITHOUT 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;
+            // 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-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/securityPatch/avcdec/Android.mk b/hostsidetests/securitybulletin/securityPatch/avcdec/Android.mk
index 033a20d..dc73233 100644
--- a/hostsidetests/securitybulletin/securityPatch/avcdec/Android.mk
+++ b/hostsidetests/securitybulletin/securityPatch/avcdec/Android.mk
@@ -28,7 +28,7 @@
 LOCAL_C_INCLUDES += external/libavc/decoder
 LOCAL_SHARED_LIBRARIES := liblog
 LOCAL_SHARED_LIBRARIES += libstagefright_soft_avcdec
-LOCAL_STATIC_LIBRARIES := libavcdec
+LOCAL_STATIC_LIBRARIES := libavcdec libubsan libsan
 
 # Tag this module as a cts test artifact
 LOCAL_COMPATIBILITY_SUITE := cts sts vts
diff --git a/hostsidetests/securitybulletin/securityPatch/hevcdec/Android.mk b/hostsidetests/securitybulletin/securityPatch/hevcdec/Android.mk
index ad66c14..9a840d9 100644
--- a/hostsidetests/securitybulletin/securityPatch/hevcdec/Android.mk
+++ b/hostsidetests/securitybulletin/securityPatch/hevcdec/Android.mk
@@ -30,7 +30,7 @@
 LOCAL_C_INCLUDES += external/libhevc/decoder
 LOCAL_SHARED_LIBRARIES := liblog
 LOCAL_SHARED_LIBRARIES += libstagefright_soft_hevcdec
-LOCAL_STATIC_LIBRARIES := libhevcdec
+LOCAL_STATIC_LIBRARIES := libhevcdec libubsan libsan
 
 # Tag this module as a cts test artifact
 LOCAL_COMPATIBILITY_SUITE := cts sts vts
diff --git a/hostsidetests/securitybulletin/securityPatch/mpeg2dec/Android.mk b/hostsidetests/securitybulletin/securityPatch/mpeg2dec/Android.mk
index 5709775..17df4d9 100644
--- a/hostsidetests/securitybulletin/securityPatch/mpeg2dec/Android.mk
+++ b/hostsidetests/securitybulletin/securityPatch/mpeg2dec/Android.mk
@@ -30,7 +30,7 @@
 LOCAL_C_INCLUDES += external/libmpeg2/decoder
 LOCAL_SHARED_LIBRARIES := liblog
 LOCAL_SHARED_LIBRARIES += libstagefright_soft_mpeg2dec
-LOCAL_STATIC_LIBRARIES := libmpeg2dec
+LOCAL_STATIC_LIBRARIES := libmpeg2dec libubsan libsan
 
 # Tag this module as a cts test artifact
 LOCAL_COMPATIBILITY_SUITE := cts sts vts
diff --git a/hostsidetests/securitybulletin/src/android/security/cts/AdbUtils.java b/hostsidetests/securitybulletin/src/android/security/cts/AdbUtils.java
index f834c38..321282b 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;
 
@@ -44,13 +43,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 pocName name of the poc binary
+     * @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 0956581..ad2d95b 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/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/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..bfd69b7 100644
--- a/hostsidetests/statsd/src/android/cts/statsd/atom/BaseTestCase.java
+++ b/hostsidetests/statsd/src/android/cts/statsd/atom/BaseTestCase.java
@@ -92,6 +92,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..fb3e711 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,54 @@
 
         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);
     }
 
     public void testKernelWakelock() throws Exception {
@@ -391,11 +422,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 +437,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 +458,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 +478,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 +544,79 @@
         assertTrue(atom.getLabel() == 1);
         assertTrue(atom.getState().getNumber() == AppBreadcrumbReported.State.START_VALUE);
     }
+
+    // Test dumpsys stats --proto.
+    public void testDumpsysStats() throws Exception {
+        // TODO: Once shell can find stats service without root, enable this test.
+        final boolean DISABLED = true;
+        if (DISABLED) {
+            return;
+        }
+        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;
+        for (ConfigMetricsReportList list : listList) {
+            ConfigMetricsReportList.ConfigKey configKey = list.getConfigKey();
+            if (configKey.getUid() == getHostUid() && configKey.getId() == CONFIG_ID) {
+                ourList = list;
+                break;
+            }
+        }
+        assertNotNull("Could not find list for uid=" + getHostUid()
+                + " 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..f37c3cf 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().getComputedPowerMilliAmpHours() > 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();
+        float 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.getPowerMilliAmpHours();
+            }
+        }
+        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());
         }
     }
 
@@ -639,117 +854,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/validation/BatteryStatsValidationTests.java b/hostsidetests/statsd/src/android/cts/statsd/validation/BatteryStatsValidationTests.java
new file mode 100644
index 0000000..193642a
--- /dev/null
+++ b/hostsidetests/statsd/src/android/cts/statsd/validation/BatteryStatsValidationTests.java
@@ -0,0 +1,264 @@
+/*
+ * Copyright (C) 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR 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 = new ValidationTestUtil().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);
+        float statsdPower = atom.getDeviceCalculatedPowerUse().getComputedPowerMilliAmpHours();
+        assertTrue("Statsd: Non-positive power value.", statsdPower > 0);
+
+        // Extract BatteryStats data
+        double bsPower = batterystatsProto.getSystem().getPowerUseSummary().getComputedPowerMah();
+        assertTrue("BatteryStats: Non-positive power value.", bsPower > 0);
+
+        assertTrue(String.format("Statsd (%f) < Batterystats (%f)", statsdPower, bsPower),
+                statsdPower > ALLOWED_FRACTIONAL_DIFFERENCE * bsPower);
+        assertTrue(String.format("Batterystats (%f) < Statsd (%f)", bsPower, statsdPower),
+                bsPower > ALLOWED_FRACTIONAL_DIFFERENCE * statsdPower);
+    }
+
+    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();
+        float statsdUidPower = 0;
+        for (Atom atom : atomList) {
+            DeviceCalculatedPowerBlameUid item = atom.getDeviceCalculatedPowerBlameUid();
+            if (item.getUid() == uid) {
+                assertFalse("Found multiple power values for uid " + uid, uidFound);
+                uidFound = true;
+                statsdUidPower = item.getPowerMilliAmpHours();
+            }
+        }
+        assertTrue("Statsd: No power value for uid " + uid, uidFound);
+        assertTrue("Statsd: Non-positive power value for uid " + uid, statsdUidPower > 0);
+
+        // Extract batterystats data
+        double bsUidPower = -1;
+        boolean hadUid = false;
+        for (UidProto uidProto : batterystatsProto.getUidsList()) {
+            if (uidProto.getUid() == uid) {
+                hadUid = true;
+                bsUidPower = uidProto.getPowerUseItem().getComputedPowerMah();
+            }
+        }
+        assertTrue("Batterystats: No power value for uid " + uid, hadUid);
+        assertTrue("BatteryStats: Non-positive power value for uid " + uid, bsUidPower > 0);
+
+        assertTrue(String.format("Statsd (%f) < Batterystats (%f).", statsdUidPower, bsUidPower),
+                statsdUidPower > ALLOWED_FRACTIONAL_DIFFERENCE * bsUidPower);
+        assertTrue(String.format("Batterystats (%f) < Statsd (%f).", bsUidPower, statsdUidPower),
+                bsUidPower > ALLOWED_FRACTIONAL_DIFFERENCE * statsdUidPower);
+    }
+
+    public void testServiceStartCount() throws Exception {
+        final String fileName = "BATTERYSTATS_SERVICE_START_COUNT.pbtxt";
+        StatsdConfig config = new ValidationTestUtil().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 = new ValidationTestUtil().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..351b1a6
--- /dev/null
+++ b/hostsidetests/statsd/src/android/cts/statsd/validation/ProcStatsValidationTests.java
@@ -0,0 +1,464 @@
+/*
+ * Copyright (C) 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR 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 = new ValidationTestUtil().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 = new ValidationTestUtil().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 = new ValidationTestUtil().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 = new ValidationTestUtil().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 = new ValidationTestUtil().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.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..c2cc382 100644
--- a/hostsidetests/theme/app/Android.mk
+++ b/hostsidetests/theme/app/Android.mk
@@ -39,5 +39,6 @@
 LOCAL_COMPATIBILITY_SUITE := cts vts general-tests
 
 LOCAL_SDK_VERSION := 23
+#LOCAL_MIN_SDK_VERSION := 17
 
 include $(BUILD_CTS_SUPPORT_PACKAGE)
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/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/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 7db9a76..90628db 100644
--- a/libs/deviceutillegacy/src/android/webkit/cts/WebViewOnUiThread.java
+++ b/libs/deviceutillegacy/src/android/webkit/cts/WebViewOnUiThread.java
@@ -59,6 +59,10 @@
  * 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 {
     /**
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/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/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..ffe684d
--- /dev/null
+++ b/tests/JobScheduler/src/android/jobscheduler/cts/JobThrottlingTest.java
@@ -0,0 +1,390 @@
+/*
+ * 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 com.android.compatibility.common.util.BatteryUtils;
+
+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;
+
+    /* 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");
+        }
+    }
+
+    @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 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 {
+        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();
+    }
+
+    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 (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..c221e56 100644
--- a/tests/accessibility/AndroidTest.xml
+++ b/tests/accessibility/AndroidTest.xml
@@ -16,6 +16,7 @@
 <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" />
     <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..f74a4bd 100644
--- a/tests/accessibility/src/android/view/accessibility/cts/AccessibilityManagerTest.java
+++ b/tests/accessibility/src/android/view/accessibility/cts/AccessibilityManagerTest.java
@@ -16,12 +16,31 @@
 
 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.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 +51,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 +74,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 +112,7 @@
         assertFalse(mAccessibilityManager.removeAccessibilityStateChangeListener(listener));
     }
 
+    @Test
     public void testAddAndRemoveTouchExplorationStateChangeListener() throws Exception {
         TouchExplorationStateChangeListener listener = (boolean enabled) -> {
             // Do nothing.
@@ -86,8 +122,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 +133,7 @@
         }.run();
     }
 
+    @Test
     public void testGetInstalledAccessibilityServicesList() throws Exception {
         List<AccessibilityServiceInfo> installedServices =
             mAccessibilityManager.getInstalledAccessibilityServiceList();
@@ -119,8 +157,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 +182,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 +201,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 +240,7 @@
     }
 
     @SuppressWarnings("deprecation")
+    @Test
     public void testGetAccessibilityServiceList() throws Exception {
         List<ServiceInfo> services = mAccessibilityManager.getAccessibilityServiceList();
         boolean speakingServiceInstalled = false;
@@ -219,23 +261,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 +292,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 +305,7 @@
         mAccessibilityManager.removeTouchExplorationStateChangeListener(listener);
     }
 
+    @Test
     public void testTouchExplorationListenerWithHandler() throws Exception {
         final Object waitObject = new Object();
         final AtomicBoolean atomicBoolean = new AtomicBoolean(false);
@@ -271,12 +317,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 +330,7 @@
         mAccessibilityManager.removeTouchExplorationStateChangeListener(listener);
     }
 
+    @Test
     public void testAccessibilityStateListenerNoHandler() throws Exception {
         final Object waitObject = new Object();
         final AtomicBoolean atomicBoolean = new AtomicBoolean(false);
@@ -295,12 +342,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 +355,7 @@
         mAccessibilityManager.removeAccessibilityStateChangeListener(listener);
     }
 
+    @Test
     public void testAccessibilityStateListenerWithHandler() throws Exception {
         final Object waitObject = new Object();
         final AtomicBoolean atomicBoolean = new AtomicBoolean(false);
@@ -319,12 +367,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 +380,96 @@
         mAccessibilityManager.removeAccessibilityStateChangeListener(listener);
     }
 
+    @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();
+        }
+    }
+
+    @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())));
+    }
+
+    @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 +489,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 +499,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..bf0abac 100644
--- a/tests/accessibilityservice/AndroidTest.xml
+++ b/tests/accessibilityservice/AndroidTest.xml
@@ -16,6 +16,7 @@
 <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" />
     <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..3fbccc7 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,109 @@
         }
         return returnValue;
     }
+
+    public static void homeScreenOrBust(Context context, UiAutomation uiAutomation) {
+        wakeUpOrBust(context, uiAutomation);
+        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/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..ad1fd91 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;
@@ -117,8 +118,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));
             }
@@ -1036,4 +1038,42 @@
             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.addCrossProfileCalendarPackage(mComponent, TEST_PACKAGE_NAME);
+            fail("addCrossProfileCalendarPackage did not throw expected SecurityException");
+        } catch (SecurityException e) {
+            assertProfileOwnerMessage(e.getMessage());
+        }
+        try {
+            mDevicePolicyManager.removeCrossProfileCalendarPackage(mComponent, TEST_PACKAGE_NAME);
+            fail("removeCrossProfileCalendarPackage 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..d90edad 100644
--- a/tests/app/AndroidTest.xml
+++ b/tests/app/AndroidTest.xml
@@ -21,7 +21,9 @@
         <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" />
diff --git a/tests/app/app/AndroidManifest.xml b/tests/app/app/AndroidManifest.xml
index fe340c9..fe35eec 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"
@@ -59,6 +60,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 +133,9 @@
         </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.LocalGrantedService"
@@ -146,6 +152,9 @@
             </intent-filter>
         </service>
 
+        <service android:name="android.app.stubs.IsolatedService" android:isolatedProcess="true">
+        </service>
+
         <activity android:name="android.app.stubs.TestedScreen"
                 android:process=":remoteScreen">
         </activity>
@@ -301,7 +310,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 +355,17 @@
             </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>
+
+        <receiver android:name="android.app.stubs.CommandReceiver"
+                  android:exported="true" />
     </application>
 
 </manifest>
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/CommandReceiver.java b/tests/app/app/src/android/app/stubs/CommandReceiver.java
new file mode 100644
index 0000000..26cc10f
--- /dev/null
+++ b/tests/app/app/src/android/app/stubs/CommandReceiver.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.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 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);
+                break;
+            case COMMAND_STOP_FOREGROUND_SERVICE:
+                doStopForegroundService(context);
+                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) {
+        Intent fgsIntent = new Intent(context, LocalForegroundService.class);
+        int command = LocalForegroundService.COMMAND_START_FOREGROUND;
+        fgsIntent.putExtras(LocalForegroundService.newCommand(new Binder(), command));
+        context.startForegroundService(fgsIntent);
+    }
+
+    private void doStopForegroundService(Context context) {
+        Intent fgsIntent = new Intent(context, LocalForegroundService.class);
+        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/LocalService.java b/tests/app/app/src/android/app/stubs/LocalService.java
index f0624b2..6120a77 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,48 @@
     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 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;
+                default:
+                    return super.onTransact(code, data, reply, flags);
             }
         }
     };
 
-
     public LocalService() {
     }
 
@@ -72,6 +97,9 @@
                 bindAction(STARTED_CODE);
             }
         }
+        if (sServiceContext == null) {
+            sServiceContext = this;
+        }
     }
 
     @Override
@@ -83,6 +111,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/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..1716299 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,182 @@
             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
+            uid3Watcher.waitFor(WatchUidRunner.CMD_PROCSTATE, WatchUidRunner.STATE_CACHED_EMPTY);
+            uid1Watcher.waitFor(WatchUidRunner.CMD_PROCSTATE, WatchUidRunner.STATE_CACHED_EMPTY);
+        } finally {
+            uid1Watcher.finish();
+            uid3Watcher.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/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..d30d553 100644
--- a/tests/app/src/android/app/cts/AutomaticZenRuleTest.java
+++ b/tests/app/src/android/app/cts/AutomaticZenRuleTest.java
@@ -21,6 +21,7 @@
 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 {
@@ -103,4 +104,15 @@
         rule.setName(mName + "new");
         assertEquals(mName + "new", rule.getName());
     }
+
+    public void testCreateRuleWithZenPolicy() {
+        ZenPolicy.Builder builder = new ZenPolicy.Builder();
+        ZenPolicy policy = builder.build();
+        builder.allowAlarms(true);
+        AutomaticZenRule rule = new AutomaticZenRule(mName, mOwner, mConditionId,
+                policy, mEnabled);
+        assertEquals(rule.getInterruptionFilter(),
+                NotificationManager.INTERRUPTION_FILTER_PRIORITY);
+        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..bd55b36 100644
--- a/tests/app/src/android/app/cts/DownloadManagerTest.java
+++ b/tests/app/src/android/app/cts/DownloadManagerTest.java
@@ -15,6 +15,13 @@
  */
 package android.app.cts;
 
+import static com.android.compatibility.common.util.SystemUtil.runShellCommand;
+
+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;
@@ -22,13 +29,17 @@
 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.ParcelFileDescriptor;
 import android.os.SystemClock;
-import android.test.AndroidTestCase;
+import android.support.test.InstrumentationRegistry;
+import android.support.test.runner.AndroidJUnit4;
 import android.text.format.DateUtils;
 import android.util.Log;
 import android.webkit.cts.CtsTestServer;
@@ -36,11 +47,24 @@
 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.InputStream;
+import java.io.InputStreamReader;
+import java.io.PrintWriter;
+import java.nio.charset.StandardCharsets;
 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 +76,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 +122,7 @@
         }
     }
 
+    @Test
     public void testDownloadManagerSupportsHttp() throws Exception {
         final DownloadCompleteReceiver receiver = new DownloadCompleteReceiver();
         try {
@@ -119,6 +145,7 @@
         }
     }
 
+    @Test
     public void testDownloadManagerSupportsHttpWithExternalWebServer() throws Exception {
         if (!hasInternetConnection()) {
             Log.i(TAG, "testDownloadManagerSupportsHttpWithExternalWebServer() ignored on device without Internet");
@@ -150,6 +177,7 @@
         }
     }
 
+    @Test
     public void testDownloadManagerSupportsHttpsWithExternalWebServer() throws Exception {
         if (!hasInternetConnection()) {
             Log.i(TAG, "testDownloadManagerSupportsHttpsWithExternalWebServer() ignored on device without Internet");
@@ -184,6 +212,7 @@
     }
 
     @CddTest(requirement="7.6.1")
+    @Test
     public void testMinimumDownload() throws Exception {
         final DownloadCompleteReceiver receiver = new DownloadCompleteReceiver();
         try {
@@ -223,6 +252,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 +309,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 +351,106 @@
         }
     }
 
+    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
+    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);
+    }
+
+    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 +608,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..92110cf 100644
--- a/tests/app/src/android/app/cts/NotificationChannelGroupTest.java
+++ b/tests/app/src/android/app/cts/NotificationChannelGroupTest.java
@@ -40,6 +40,8 @@
         assertFalse(group.isBlocked());
         assertNull(group.getDescription());
         assertEquals(0, group.getChannels().size());
+        assertEquals(0, group.getUserLockedFields());
+        assertTrue(group.canOverlayApps());
     }
 
     public void testIsBlocked() {
@@ -48,10 +50,21 @@
         assertTrue(group.isBlocked());
     }
 
+    public void testAppOverlay() {
+        NotificationChannelGroup group =  new NotificationChannelGroup("1", "one");
+        group.setAllowAppOverlay(false);
+        assertFalse(group.canOverlayApps());
+
+        group.setAllowAppOverlay(true);
+        assertTrue(group.canOverlayApps());
+    }
+
     public void testWriteToParcel() {
         NotificationChannelGroup group = new NotificationChannelGroup("1", "one");
         group.setBlocked(true);
         group.setDescription("bananas!");
+        group.lockFields(NotificationChannelGroup.USER_LOCKED_ALLOW_APP_OVERLAY);
+        group.setAllowAppOverlay(false);
         Parcel parcel = Parcel.obtain();
         group.writeToParcel(parcel, 0);
         parcel.setDataPosition(0);
@@ -64,10 +77,15 @@
         NotificationChannelGroup group =  new NotificationChannelGroup("1", "one");
         group.setBlocked(true);
         group.setDescription("bananas");
+        group.lockFields(NotificationChannelGroup.USER_LOCKED_ALLOW_APP_OVERLAY);
+        group.setAllowAppOverlay(false);
         NotificationChannelGroup cloned = group.clone();
         assertEquals("1", cloned.getId());
         assertEquals("one", cloned.getName());
         assertTrue(cloned.isBlocked());
         assertEquals("bananas", cloned.getDescription());
+        assertEquals(NotificationChannelGroup.USER_LOCKED_ALLOW_APP_OVERLAY,
+                group.getUserLockedFields());
+        assertFalse(cloned.canOverlayApps());
     }
 }
diff --git a/tests/app/src/android/app/cts/NotificationChannelTest.java b/tests/app/src/android/app/cts/NotificationChannelTest.java
index 70ec9cf..b369703 100644
--- a/tests/app/src/android/app/cts/NotificationChannelTest.java
+++ b/tests/app/src/android/app/cts/NotificationChannelTest.java
@@ -17,6 +17,7 @@
 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;
@@ -44,7 +45,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 +53,13 @@
         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.canOverlayApps());
     }
 
     public void testWriteToParcel() {
@@ -152,4 +154,19 @@
         channel.setGroup("banana");
         assertEquals("banana", channel.getGroup());
     }
+
+    public void testAppOverlay() {
+        NotificationChannel channel =
+                new NotificationChannel("1", "one", IMPORTANCE_DEFAULT);
+        channel.setAllowAppOverlay(true);
+        assertEquals("Only HIGH channels can have app overlays", false, channel.canOverlayApps());
+
+        channel = new NotificationChannel("1", "one", IMPORTANCE_HIGH);
+        channel.setAllowAppOverlay(false);
+        assertEquals(false, channel.canOverlayApps());
+
+        channel = new NotificationChannel("1", "one", IMPORTANCE_HIGH);
+        channel.setAllowAppOverlay(true);
+        assertEquals(true, channel.canOverlayApps());
+    }
 }
diff --git a/tests/app/src/android/app/cts/NotificationManagerTest.java b/tests/app/src/android/app/cts/NotificationManagerTest.java
index 3f61605..97f24bc 100644
--- a/tests/app/src/android/app/cts/NotificationManagerTest.java
+++ b/tests/app/src/android/app/cts/NotificationManagerTest.java
@@ -34,8 +34,8 @@
 import android.app.NotificationManager;
 import android.app.PendingIntent;
 import android.app.UiAutomation;
-import android.app.stubs.MockNotificationListener;
 import android.app.stubs.R;
+import android.app.stubs.TestNotificationListener;
 import android.content.ComponentName;
 import android.content.Context;
 import android.content.Intent;
@@ -76,7 +76,7 @@
     private NotificationManager mNotificationManager;
     private ActivityManager mActivityManager;
     private String mId;
-    private MockNotificationListener mListener;
+    private TestNotificationListener mListener;
 
     @Override
     protected void setUp() throws Exception {
@@ -110,7 +110,7 @@
             mNotificationManager.deleteNotificationChannel(nc.getId());
         }
 
-        toggleListenerAccess(MockNotificationListener.getId(),
+        toggleListenerAccess(TestNotificationListener.getId(),
                 InstrumentationRegistry.getInstrumentation(), false);
 
         List<NotificationChannelGroup> groups = mNotificationManager.getNotificationChannelGroups();
@@ -201,6 +201,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 +505,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 +546,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();
 
@@ -901,6 +978,79 @@
                 InstrumentationRegistry.getInstrumentation(), false);
     }
 
+    public void testPostFullScreenIntent_permission() {
+        int id = 6000;
+
+        final Notification notification =
+                new Notification.Builder(mContext, NOTIFICATION_CHANNEL_ID)
+                        .setSmallIcon(R.drawable.black)
+                        .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;
+    }
+
+    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));
+    }
+
     private PendingIntent getPendingIntent() {
         return PendingIntent.getActivity(
                 getContext(), 0, new Intent(getContext(), this.getClass()), 0);
@@ -912,6 +1062,12 @@
 
     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())
@@ -934,6 +1090,12 @@
 
     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
@@ -1067,8 +1229,7 @@
 
     private void suspendPackage(String packageName,
             Instrumentation instrumentation, boolean suspend) throws IOException {
-        String command = " cmd notification " + (suspend ? "suspend_package "
-                : "unsuspend_package ") + packageName;
+        String command = " cmd package " + (suspend ? "suspend " : "unsuspend ") + packageName;
 
         runCommand(command, instrumentation);
     }
@@ -1082,7 +1243,7 @@
         runCommand(command, instrumentation);
 
         final NotificationManager nm = mContext.getSystemService(NotificationManager.class);
-        final ComponentName listenerComponent = MockNotificationListener.getComponentName();
+        final ComponentName listenerComponent = TestNotificationListener.getComponentName();
         assertTrue(listenerComponent + " has not been granted access",
                 nm.isNotificationListenerAccessGranted(listenerComponent) == on);
     }
@@ -1103,4 +1264,8 @@
             uiAutomation.destroy();
         }
     }
+
+    private Instrumentation getInstrumentation() {
+        return InstrumentationRegistry.getInstrumentation();
+    }
 }
diff --git a/tests/app/src/android/app/cts/NotificationTest.java b/tests/app/src/android/app/cts/NotificationTest.java
index 3549145..9c4a0ff 100644
--- a/tests/app/src/android/app/cts/NotificationTest.java
+++ b/tests/app/src/android/app/cts/NotificationTest.java
@@ -62,6 +62,7 @@
             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 +121,15 @@
     }
 
     public void testWriteToParcel() {
-
+        PendingIntent overlayIntent = PendingIntent.getActivity(mContext, 0, new Intent(), 0);
         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)
+                .setAppOverlayIntent(overlayIntent)
+                .setAllowSystemGeneratedContextualActions(ALLOW_SYS_GEN_CONTEXTUAL_ACTIONS)
                 .build();
         mNotification.icon = 0;
         mNotification.number = 1;
@@ -183,6 +186,9 @@
         assertEquals(mNotification.getChannelId(), result.getChannelId());
         assertEquals(mNotification.getSettingsText(), result.getSettingsText());
         assertEquals(mNotification.getGroupAlertBehavior(), result.getGroupAlertBehavior());
+        assertEquals(overlayIntent, result.getAppOverlayIntent());
+        assertEquals(mNotification.getAllowSystemGeneratedContextualActions(),
+                result.getAllowSystemGeneratedContextualActions());
 
         mNotification.contentIntent = null;
         parcel = Parcel.obtain();
@@ -233,6 +239,7 @@
     public void testBuilder() {
         final Intent intent = new Intent();
         final PendingIntent contentIntent = PendingIntent.getBroadcast(mContext, 0, intent, 0);
+        PendingIntent overlayIntent = PendingIntent.getActivity(mContext, 0, intent, 0);
         mNotification = new Notification.Builder(mContext, CHANNEL.getId())
                 .setSmallIcon(1)
                 .setContentTitle(CONTENT_TITLE)
@@ -243,6 +250,8 @@
                 .setTimeoutAfter(TIMEOUT)
                 .setSettingsText(SETTING_TEXT)
                 .setGroupAlertBehavior(Notification.GROUP_ALERT_SUMMARY)
+                .setAppOverlayIntent(overlayIntent)
+                .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 +263,9 @@
         assertEquals(TIMEOUT, mNotification.getTimeoutAfter());
         assertEquals(SETTING_TEXT, mNotification.getSettingsText());
         assertEquals(Notification.GROUP_ALERT_SUMMARY, mNotification.getGroupAlertBehavior());
+        assertEquals(overlayIntent, mNotification.getAppOverlayIntent());
+        assertEquals(ALLOW_SYS_GEN_CONTEXTUAL_ACTIONS,
+                mNotification.getAllowSystemGeneratedContextualActions());
     }
 
     public void testBuilder_getStyle() {
@@ -483,6 +495,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)
+                .setSemanticAction(Notification.Action.SEMANTIC_ACTION_CONTEXTUAL_SUGGESTION);
+        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 */)
+                .setSemanticAction(Notification.Action.SEMANTIC_ACTION_CONTEXTUAL_SUGGESTION);
+        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 +560,18 @@
         }
     }
 
+    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());
+    }
+
     private static RemoteInput newDataOnlyRemoteInput() {
         return new RemoteInput.Builder(DATA_RESULT_KEY)
             .setAllowFreeFormInput(false)
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..3f32a86 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 {
@@ -73,6 +87,7 @@
     private Intent mLocalGrantedService;
     private Intent mLocalService_ApplicationHasPermission;
     private Intent mLocalService_ApplicationDoesNotHavePermission;
+    private Intent mIsolatedService;
     private Intent mExternalService;
 
     private IBinder mStateReceiver;
@@ -190,6 +205,151 @@
         }
     }
 
+    final class IsolatedConnection implements ServiceConnection {
+        private IBinder mService;
+        private int mUid;
+        private int mPid;
+
+        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 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 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();
+                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 +601,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 +1053,443 @@
             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;
+            }
+        }
+    }
+
+    /**
+     * 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/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/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/autofillservice/AndroidManifest.xml b/tests/autofillservice/AndroidManifest.xml
index 5feaf8d..2cae52a 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" />
 
@@ -30,7 +28,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" />
@@ -53,7 +51,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" />
@@ -72,18 +70,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" />
@@ -95,6 +94,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"
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/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..3ee3e79 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;
@@ -36,6 +39,7 @@
   */
 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..00135d4 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);
+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/AuthenticationTest.java b/tests/autofillservice/src/android/autofillservice/cts/AuthenticationTest.java
index 6e3308d..cb98b54 100644
--- a/tests/autofillservice/src/android/autofillservice/cts/AuthenticationTest.java
+++ b/tests/autofillservice/src/android/autofillservice/cts/AuthenticationTest.java
@@ -725,7 +725,7 @@
         final String expectedMessage = getWelcomeMessage("malkovich");
         final String actualMessage = mActivity.tapLogin();
         assertWithMessage("Wrong welcome msg").that(actualMessage).isEqualTo(expectedMessage);
-        mUiBot.saveForAutofill(true, SAVE_DATA_TYPE_PASSWORD);
+        mUiBot.updateForAutofill(true, SAVE_DATA_TYPE_PASSWORD);
 
         // Assert client state on authentication activity.
         assertClientState("auth activity", AuthenticationActivity.getData(), "CSI", "FromResponse");
@@ -1105,7 +1105,7 @@
         final String expectedMessage = getWelcomeMessage("malkovich");
         final String actualMessage = mActivity.tapLogin();
         assertWithMessage("Wrong welcome msg").that(actualMessage).isEqualTo(expectedMessage);
-        mUiBot.saveForAutofill(true, SAVE_DATA_TYPE_PASSWORD);
+        mUiBot.updateForAutofill(true, SAVE_DATA_TYPE_PASSWORD);
 
         // Assert client state on authentication activity.
         assertClientState("auth activity", AuthenticationActivity.getData(), "CSI", "FromResponse");
diff --git a/tests/autofillservice/src/android/autofillservice/cts/AutoFillServiceTestCase.java b/tests/autofillservice/src/android/autofillservice/cts/AutoFillServiceTestCase.java
index 69b749a..e04a2fc 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,292 @@
 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";
+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.
+         */
+        protected int getNumberRetries() {
+            return 1;
+        }
+
+        /**
+         * 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..c03ba04 100644
--- a/tests/autofillservice/src/android/autofillservice/cts/CannedFillResponse.java
+++ b/tests/autofillservice/src/android/autofillservice/cts/CannedFillResponse.java
@@ -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,6 +117,8 @@
         mDisableDuration = builder.mDisableDuration;
         mFieldClassificationIds = builder.mFieldClassificationIds;
         mFieldClassificationIdsOverflow = builder.mFieldClassificationIdsOverflow;
+        mSaveInfoDecorator = builder.mSaveInfoDecorator;
+        mUserData = builder.mUserData;
     }
 
     /**
@@ -131,6 +135,12 @@
             new Builder(ResponseType.TIMEOUT).build();
 
 
+    /**
+     * Constant used to call {@link FillCallback#onFailure(CharSequence)} method.
+     */
+    static final CannedFillResponse FAIL =
+            new Builder(ResponseType.FAILURE).build();
+
     String getFailureMessage() {
         return mFailureMessage;
     }
@@ -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,13 +270,16 @@
                 + ", disableDuration=" + mDisableDuration
                 + ", fieldClassificationIds=" + Arrays.toString(mFieldClassificationIds)
                 + ", fieldClassificationIdsOverflow=" + mFieldClassificationIdsOverflow
+                + ", saveInfoDecorator=" + mSaveInfoDecorator
+                + ", userData=" + mUserData
                 + "]";
     }
 
     enum ResponseType {
         NORMAL,
         NULL,
-        TIMEOUT
+        TIMEOUT,
+        FAILURE
     }
 
     static class Builder {
@@ -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;
+        }
     }
 
     /**
@@ -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/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 c9afacc..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,13 +76,13 @@
         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.saveForAutofill(true, SAVE_DATA_TYPE_GENERIC);
+        mUiBot.updateForAutofill(true, SAVE_DATA_TYPE_GENERIC);
         final SaveRequest saveRequest = sReplier.getNextSaveRequest();
         assertWithMessage("onSave() not called").that(saveRequest).isNotNull();
 
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..b0127ff 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,61 @@
         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);
     }
 
     /*
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 4e5418b..b6ebd7a 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,6 +33,7 @@
 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.content.ComponentName;
 import android.content.Context;
@@ -62,13 +62,14 @@
 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;
 
 /**
@@ -90,15 +91,19 @@
     static final String ID_OUTPUT = "output";
     static final String ID_STATIC_TEXT = "static_text";
 
-    public static final String NULL_DATASET_ID = null;
+    static final String NULL_DATASET_ID = null;
+
+    static final char LARGE_STRING_CHAR = '6';
+    // NOTE: cannot be much large as it could ANR and fail the test.
+    static final int LARGE_STRING_SIZE = 100_000;
+    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";
+    static final String UNUSED_AUTOFILL_VALUE = null;
 
     private static final String ACCELLEROMETER_CHANGE =
             "content insert --uri content://settings/system --bind name:s:accelerometer_rotation "
@@ -107,6 +112,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);
+
     /**
      * Helper interface used to filter nodes.
      *
@@ -746,7 +755,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();
 
@@ -755,6 +764,20 @@
     }
 
     /**
+     * Get an {@link AutofillId} mapped from the {@code structure} node with the given
+     * {@code resourceId}.
+     */
+    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 {
@@ -779,16 +802,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;
+        });
     }
 
     /**
@@ -831,43 +866,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() {
@@ -875,21 +912,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) {
@@ -1028,11 +1050,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]);
         }
@@ -1122,10 +1144,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)
                 });
     }
 
@@ -1218,10 +1240,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);
         }
@@ -1232,38 +1252,111 @@
     }
 
     @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) throws IOException {
+        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);
+    }
+
     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..7309db6 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();
     }
 
     /**
@@ -269,7 +288,7 @@
 
     static void resetStaticState() {
         sInstance.set(null);
-        sConnected = false;
+        sConnected.set(false);
         sServiceLabel = SERVICE_CLASS;
     }
 
@@ -407,8 +426,9 @@
          * Sets the {@link IntentSender} that is passed to
          * {@link SaveCallback#onSuccess(IntentSender)}.
          */
-        void setOnSave(IntentSender intentSender) {
+        Replier setOnSave(IntentSender intentSender) {
             mOnSaveIntentSender = intentSender;
+            return this;
         }
 
         /**
@@ -554,6 +574,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 +611,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..8690204 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;
@@ -56,6 +55,7 @@
     static final String BACKDOOR_USERNAME = "LemmeIn";
     static final String BACKDOOR_PASSWORD_SUBSTRING = "pass";
 
+    private LinearLayout mUsernameContainer;
     private TextView mUsernameLabel;
     private EditText mUsernameEditText;
     private TextView mPasswordLabel;
@@ -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() {
@@ -253,6 +218,13 @@
     }
 
     /**
+     * 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() {
diff --git a/tests/autofillservice/src/android/autofillservice/cts/LoginActivityTest.java b/tests/autofillservice/src/android/autofillservice/cts/LoginActivityTest.java
index c476c7c..5b42ad8 100644
--- a/tests/autofillservice/src/android/autofillservice/cts/LoginActivityTest.java
+++ b/tests/autofillservice/src/android/autofillservice/cts/LoginActivityTest.java
@@ -17,6 +17,7 @@
 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;
@@ -41,7 +42,9 @@
 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 +66,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 +91,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 +376,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 +740,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();
@@ -854,7 +893,7 @@
         assertWithMessage("Wrong welcome msg").that(actualMessage).isEqualTo(expectedMessage);
 
         // Assert the snack bar is shown and tap "Save".
-        mUiBot.saveForAutofill(true, SAVE_DATA_TYPE_PASSWORD);
+        mUiBot.updateForAutofill(true, SAVE_DATA_TYPE_PASSWORD);
 
         final SaveRequest saveRequest = sReplier.getNextSaveRequest();
 
@@ -957,7 +996,7 @@
             assertWithMessage("Wrong welcome msg").that(actualMessage).isEqualTo(expectedMessage);
 
             // Assert the snack bar is shown and tap "Save".
-            mUiBot.saveForAutofill(true, SAVE_DATA_TYPE_PASSWORD);
+            mUiBot.updateForAutofill(true, SAVE_DATA_TYPE_PASSWORD);
 
             final SaveRequest saveRequest = sReplier.getNextSaveRequest();
 
@@ -972,13 +1011,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
+                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]);
+                });
+            } catch (Exception e) {
+                mSafeCleanerRule.add(e);
+            }
         }
     }
 
@@ -1925,6 +1968,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 +2010,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 +2020,9 @@
 
         // Check the results.
         mActivity.assertAutoFilled();
+
+        // clear clipboard
+        cm.clearPrimaryClip();
     }
 
     @Test
@@ -2154,6 +2232,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 +2242,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 +2298,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 +2605,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 8474d28..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
@@ -115,7 +117,7 @@
         assertWithMessage("Wrong welcome msg").that(actualMessage).isEqualTo(expectedMessage);
 
         // Assert the snack bar is shown and tap "Save".
-        mUiBot.saveForAutofill(true, SAVE_DATA_TYPE_PASSWORD);
+        mUiBot.updateForAutofill(true, SAVE_DATA_TYPE_PASSWORD);
 
         final SaveRequest saveRequest = sReplier.getNextSaveRequest();
 
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/OptionalSaveActivity.java b/tests/autofillservice/src/android/autofillservice/cts/OptionalSaveActivity.java
index e326231..561b727 100644
--- a/tests/autofillservice/src/android/autofillservice/cts/OptionalSaveActivity.java
+++ b/tests/autofillservice/src/android/autofillservice/cts/OptionalSaveActivity.java
@@ -25,6 +25,8 @@
 import android.widget.Button;
 import android.widget.EditText;
 
+import androidx.annotation.Nullable;
+
 /**
  * Activity that has the following fields:
  *
@@ -105,12 +107,21 @@
      * Sets the expectation for an auto-fill request, so it can be asserted through
      * {@link #assertAutoFilled()} later.
      */
-    void expectAutoFill(String address1, String address2, String city, String favColor) {
+    void expectAutoFill(@Nullable String address1, @Nullable String address2, @Nullable String city,
+            @Nullable String favColor) {
         mExpectation = new FillExpectation(address1, address2, city, favColor);
-        mAddress1.addTextChangedListener(mExpectation.address1Watcher);
-        mAddress2.addTextChangedListener(mExpectation.address2Watcher);
-        mCity.addTextChangedListener(mExpectation.cityWatcher);
-        mFavoriteColor.addTextChangedListener(mExpectation.favoriteColorWatcher);
+        if (address1 != null) {
+            mAddress1.addTextChangedListener(mExpectation.address1Watcher);
+        }
+        if (address2 != null) {
+            mAddress2.addTextChangedListener(mExpectation.address2Watcher);
+        }
+        if (city != null) {
+            mCity.addTextChangedListener(mExpectation.cityWatcher);
+        }
+        if (favColor != null) {
+            mFavoriteColor.addTextChangedListener(mExpectation.favoriteColorWatcher);
+        }
     }
 
     /**
@@ -119,10 +130,18 @@
      */
     void assertAutoFilled() throws Exception {
         assertWithMessage("expectAutoFill() not called").that(mExpectation).isNotNull();
-        mExpectation.address1Watcher.assertAutoFilled();
-        mExpectation.address2Watcher.assertAutoFilled();
-        mExpectation.cityWatcher.assertAutoFilled();
-        mExpectation.favoriteColorWatcher.assertAutoFilled();
+        if (mExpectation.address1Watcher != null) {
+            mExpectation.address1Watcher.assertAutoFilled();
+        }
+        if (mExpectation.address2Watcher != null) {
+            mExpectation.address2Watcher.assertAutoFilled();
+        }
+        if (mExpectation.cityWatcher != null) {
+            mExpectation.cityWatcher.assertAutoFilled();
+        }
+        if (mExpectation.favoriteColorWatcher != null) {
+            mExpectation.favoriteColorWatcher.assertAutoFilled();
+        }
     }
 
     /**
@@ -134,11 +153,15 @@
         private final OneTimeTextWatcher cityWatcher;
         private final OneTimeTextWatcher favoriteColorWatcher;
 
-        private FillExpectation(String address1, String address2, String city, String favColor) {
-            address1Watcher = new OneTimeTextWatcher("address1", mAddress1, address1);
-            address2Watcher = new OneTimeTextWatcher("address2", mAddress2, address2);
-            cityWatcher = new OneTimeTextWatcher("city", mCity, city);
-            favoriteColorWatcher = new OneTimeTextWatcher("favColor", mFavoriteColor, favColor);
+        private FillExpectation(@Nullable String address1, @Nullable String address2,
+                @Nullable String city, @Nullable String favColor) {
+            address1Watcher = address1 == null ? null
+                    : new OneTimeTextWatcher("address1", mAddress1, address1);
+            address2Watcher = address2 == null ? null
+                    : new OneTimeTextWatcher("address2", mAddress2, address2);
+            cityWatcher = city == null ? null : new OneTimeTextWatcher("city", mCity, city);
+            favoriteColorWatcher = favColor == null ? null
+                    : new OneTimeTextWatcher("favColor", mFavoriteColor, favColor);
         }
     }
 }
diff --git a/tests/autofillservice/src/android/autofillservice/cts/OptionalSaveActivityTest.java b/tests/autofillservice/src/android/autofillservice/cts/OptionalSaveActivityTest.java
index 5aaedde..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();
+            }
+        };
     }
 
     /**
@@ -322,7 +322,7 @@
         mActivity.save();
 
         // Assert the snack bar is shown and tap "Save".
-        mUiBot.saveForAutofill(true, SAVE_DATA_TYPE_ADDRESS);
+        mUiBot.updateForAutofill(true, SAVE_DATA_TYPE_ADDRESS);
 
         final SaveRequest saveRequest = sReplier.getNextSaveRequest();
         assertWithMessage("onSave() not called").that(saveRequest).isNotNull();
@@ -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
@@ -652,7 +704,7 @@
         mActivity.save();
 
         // ...and make sure the snack bar is shown.
-        mUiBot.saveForAutofill(true, SAVE_DATA_TYPE_ADDRESS);
+        mUiBot.updateForAutofill(true, SAVE_DATA_TYPE_ADDRESS);
 
         // Finally, assert values.
         final SaveRequest saveRequest = sReplier.getNextSaveRequest();
@@ -664,4 +716,53 @@
         assertTextAndValue(findNodeByResourceId(saveRequest.structure, ID_FAVORITE_COLOR),
                 "Yellow");
     }
+
+    @Test
+    public void testShowUpdateWhenUserChangedOptionalValueFromDatasetAndRequiredNotFromDataset()
+            throws Exception {
+        // Set service.
+        enableService();
+
+        // Address 2 will be required but not available
+        mActivity.expectAutoFill("742 Evergreen Terrace", null, "Springfield", "Yellow");
+        // Set expectations.
+        sReplier.addResponse(new CannedFillResponse.Builder()
+                .setRequiredSavableIds(SAVE_DATA_TYPE_ADDRESS, ID_ADDRESS1, ID_ADDRESS2)
+                .setOptionalSavableIds(ID_CITY)
+                .addDataset(new CannedDataset.Builder()
+                        .setPresentation(createPresentation("SF"))
+                        .setField(ID_ADDRESS1, "742 Evergreen Terrace")
+                        .setField(ID_CITY, "Springfield")
+                        .setField(ID_FAVORITE_COLOR, "Yellow")
+                        .build())
+                .build());
+
+        // Trigger autofill.
+        mActivity.syncRunOnUiThread(() -> mActivity.mAddress1.requestFocus());
+        sReplier.getNextFillRequest();
+
+        mUiBot.selectDataset("SF");
+        mActivity.assertAutoFilled();
+
+        // Change required and optional field.
+        mActivity.syncRunOnUiThread(() -> {
+            mActivity.mAddress2.setText("Simpsons House");
+            mActivity.mCity.setText("Shelbyville");
+        });
+        // Trigger save...
+        mActivity.save();
+
+        // ...and make sure the snack bar is shown.
+        mUiBot.updateForAutofill(true, SAVE_DATA_TYPE_ADDRESS);
+
+        // Finally, assert values.
+        final SaveRequest saveRequest = sReplier.getNextSaveRequest();
+        assertTextAndValue(findNodeByResourceId(saveRequest.structure, ID_ADDRESS1),
+                "742 Evergreen Terrace");
+        assertTextAndValue(findNodeByResourceId(saveRequest.structure, ID_ADDRESS2),
+                "Simpsons House");
+        assertTextAndValue(findNodeByResourceId(saveRequest.structure, ID_CITY), "Shelbyville");
+        assertTextAndValue(findNodeByResourceId(saveRequest.structure, ID_FAVORITE_COLOR),
+                "Yellow");
+    }
 }
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 eb59da4..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() {
@@ -122,7 +125,7 @@
             mActivity.mPassword.setText("PASS");
             mActivity.mCommit.performClick();
         });
-        final UiObject2 saveUi = mUiBot.assertSaveShowing(SAVE_DATA_TYPE_GENERIC);
+        final UiObject2 saveUi = mUiBot.assertUpdateShowing(SAVE_DATA_TYPE_GENERIC);
 
         // Save it...
         mUiBot.saveForAutofill(saveUi, true);
@@ -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.
@@ -177,7 +240,7 @@
         input.setText("ID");
         password.setText("PASS");
         mUiBot.assertShownByRelativeId(ID_COMMIT).click();
-        mUiBot.saveForAutofill(true, SAVE_DATA_TYPE_GENERIC);
+        mUiBot.updateForAutofill(true, SAVE_DATA_TYPE_GENERIC);
 
         // ... and assert results
         final SaveRequest saveRequest = sReplier.getNextSaveRequest();
@@ -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(() -> {
@@ -797,7 +987,7 @@
             mActivity.mPassword.setText("PASS");
             mActivity.mCommit.performClick();
         });
-        final UiObject2 saveUi = mUiBot.assertSaveShowing(SAVE_DATA_TYPE_GENERIC);
+        final UiObject2 saveUi = mUiBot.assertUpdateShowing(SAVE_DATA_TYPE_GENERIC);
 
         // Save it...
         mUiBot.saveForAutofill(saveUi, true);
@@ -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 4bd8cf0..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,13 +75,13 @@
         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.saveForAutofill(true, SAVE_DATA_TYPE_GENERIC);
+        mUiBot.updateForAutofill(true, SAVE_DATA_TYPE_GENERIC);
         final SaveRequest saveRequest = sReplier.getNextSaveRequest();
         assertWithMessage("onSave() not called").that(saveRequest).isNotNull();
 
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 5eefb0d..3c520bc 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;
@@ -74,6 +78,8 @@
     private static final String RESOURCE_ID_SAVE_TITLE = "autofill_save_title";
     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 =
@@ -87,6 +93,11 @@
             "autofill_save_type_email_address";
     private static final String RESOURCE_STRING_SAVE_BUTTON_NOT_NOW = "save_password_notnow";
     private static final String RESOURCE_STRING_SAVE_BUTTON_NO_THANKS = "autofill_save_no";
+    private static final String RESOURCE_STRING_SAVE_BUTTON_YES = "autofill_save_yes";
+    private static final String RESOURCE_STRING_UPDATE_BUTTON_YES = "autofill_update_yes";
+    private static final String RESOURCE_STRING_UPDATE_TITLE = "autofill_update_title";
+    private static final String RESOURCE_STRING_UPDATE_TITLE_WITH_TYPE =
+            "autofill_update_title_with_type";
 
     private static final String RESOURCE_STRING_AUTOFILL = "autofill";
     private static final String RESOURCE_STRING_DATASET_PICKER_ACCESSIBILITY_TITLE =
@@ -132,12 +143,52 @@
         mAutoman = instrumentation.getUiAutomation();
     }
 
+    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");
+    }
+
     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).
+     */
+    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.
+     */
+    void setScreenResolution() {
+        assumeMinimumResolution(500);
+
+        runShellCommand("wm size 1080x1920");
+        runShellCommand("wm density 320");
+    }
+
+    /**
+     * Resets the screen resolution.
+     *
+     * <p>Should always be called after {@link #setScreenResolution()}.
+     */
+    void resetScreenResolution() {
+        runShellCommand("wm density reset");
+        runShellCommand("wm size reset");
     }
 
     /**
@@ -309,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.
      *
@@ -316,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();
     }
 
     /**
@@ -377,8 +438,22 @@
      * <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());
+    void assertGoneByRelativeId(@NonNull String id, @NonNull Timeout timeout) {
+        assertGoneByRelativeId(/* parent = */ null, id, timeout);
+    }
+
+    /**
+     * 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.
+     */
+    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";
@@ -437,6 +512,14 @@
     }
 
     /**
+     * Asserts the save snackbar is showing with the Update message and returns it.
+     */
+    UiObject2 assertUpdateShowing(int... types) throws Exception {
+        return assertSaveOrUpdateShowing(/* update= */ true, SaveInfo.NEGATIVE_BUTTON_STYLE_CANCEL,
+                null, SAVE_TIMEOUT, types);
+    }
+
+    /**
      * Presses the Back button.
      */
     void pressBack() {
@@ -459,6 +542,10 @@
         assertNeverShown("save UI for type " + type, SAVE_UI_SELECTOR, SAVE_NOT_SHOWN_NAPTIME_MS);
     }
 
+    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) {
@@ -484,23 +571,25 @@
     }
 
     UiObject2 assertSaveShowing(String description, int... types) throws Exception {
-        return assertSaveShowing(SaveInfo.NEGATIVE_BUTTON_STYLE_CANCEL, description,
-                SAVE_TIMEOUT, types);
+        return assertSaveOrUpdateShowing(/* update= */ false, SaveInfo.NEGATIVE_BUTTON_STYLE_CANCEL,
+                description, SAVE_TIMEOUT, types);
     }
 
     UiObject2 assertSaveShowing(String description, Timeout timeout, int... types)
             throws Exception {
-        return assertSaveShowing(SaveInfo.NEGATIVE_BUTTON_STYLE_CANCEL, description, timeout,
-                types);
+        return assertSaveOrUpdateShowing(/* update= */ false, SaveInfo.NEGATIVE_BUTTON_STYLE_CANCEL,
+                description, timeout, types);
     }
 
     UiObject2 assertSaveShowing(int negativeButtonStyle, String description,
             int... types) throws Exception {
-        return assertSaveShowing(negativeButtonStyle, description, SAVE_TIMEOUT, types);
+        return assertSaveOrUpdateShowing(/* update= */ false, negativeButtonStyle, description,
+                SAVE_TIMEOUT, types);
     }
 
-    UiObject2 assertSaveShowing(int negativeButtonStyle, String description, Timeout timeout,
-            int... types) throws Exception {
+
+    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 =
@@ -516,13 +605,21 @@
         final String actualTitle = titleView.getText();
         Log.d(TAG, "save title: " + actualTitle);
 
+        final String titleId, titleWithTypeId;
+        if (update) {
+            titleId = RESOURCE_STRING_UPDATE_TITLE;
+            titleWithTypeId = RESOURCE_STRING_UPDATE_TITLE_WITH_TYPE;
+        } else {
+            titleId = RESOURCE_STRING_SAVE_TITLE;
+            titleWithTypeId = RESOURCE_STRING_SAVE_TITLE_WITH_TYPE;
+        }
+
         final String serviceLabel = InstrumentedAutoFillService.getServiceLabel();
         switch (types.length) {
             case 1:
                 final String expectedTitle = (types[0] == SAVE_DATA_TYPE_GENERIC)
-                        ? Html.fromHtml(getString(RESOURCE_STRING_SAVE_TITLE,
-                                serviceLabel), 0).toString()
-                        : Html.fromHtml(getString(RESOURCE_STRING_SAVE_TITLE_WITH_TYPE,
+                        ? Html.fromHtml(getString(titleId, serviceLabel), 0).toString()
+                        : Html.fromHtml(getString(titleWithTypeId,
                                 getSaveTypeString(types[0]), serviceLabel), 0).toString();
                 assertThat(actualTitle).isEqualTo(expectedTitle);
                 break;
@@ -546,6 +643,14 @@
             assertWithMessage("save subtitle(%s)", description).that(saveSubTitle).isNotNull();
         }
 
+        final String positiveButtonStringId = update ? RESOURCE_STRING_UPDATE_BUTTON_YES
+                : RESOURCE_STRING_SAVE_BUTTON_YES;
+        final String expectedPositiveButtonText = getString(positiveButtonStringId).toUpperCase();
+        final UiObject2 positiveButton = waitForObject(snackbar,
+                By.res("android", RESOURCE_ID_SAVE_BUTTON_YES), timeout);
+        assertWithMessage("wrong text on positive button")
+                .that(positiveButton.getText().toUpperCase()).isEqualTo(expectedPositiveButtonText);
+
         final String negativeButtonStringId =
                 (negativeButtonStyle == SaveInfo.NEGATIVE_BUTTON_STYLE_REJECT)
                 ? RESOURCE_STRING_SAVE_BUTTON_NOT_NOW
@@ -575,6 +680,11 @@
         saveForAutofill(saveSnackBar, yesDoIt);
     }
 
+    public void updateForAutofill(boolean yesDoIt, int... types) throws Exception {
+        final UiObject2 saveUi = assertUpdateShowing(types);
+        saveForAutofill(saveUi, yesDoIt);
+    }
+
     /**
      * Taps an option in the save snackbar.
      *
@@ -610,15 +720,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 {
+    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();
@@ -681,13 +814,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);
     }
@@ -698,18 +833,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:
@@ -719,6 +866,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.
      *
@@ -796,25 +947,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();
         }
     }
 
@@ -830,6 +977,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/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 8623967..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,28 +266,20 @@
         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.saveForAutofill(true, SAVE_DATA_TYPE_PASSWORD);
+        mUiBot.updateForAutofill(true, SAVE_DATA_TYPE_PASSWORD);
 
         // Assert results
         final SaveRequest saveRequest = sReplier.getNextSaveRequest();
@@ -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,23 +380,23 @@
 
         // 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);
+        mUiBot.updateForAutofill(true, SAVE_DATA_TYPE_PASSWORD);
 
         // Assert results
         final SaveRequest saveRequest = sReplier.getNextSaveRequest();
@@ -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/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..2a97aee 100644
--- a/tests/autofillservice/src/android/autofillservice/cts/common/ShellHelper.java
+++ b/tests/autofillservice/src/android/autofillservice/cts/common/ShellHelper.java
@@ -16,12 +16,13 @@
 
 package android.autofillservice.cts.common;
 
-import androidx.annotation.NonNull;
 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 +66,13 @@
     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);
+    }
 }
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..f9bd57d 100644
--- a/tests/backup/AndroidTest.xml
+++ b/tests/backup/AndroidTest.xml
@@ -17,11 +17,14 @@
 <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" />
     <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..349083d 100644
--- a/tests/camera/Android.mk
+++ b/tests/camera/Android.mk
@@ -46,7 +46,9 @@
 	mockito-target-minus-junit4 \
 	android-ex-camera2 \
 	CtsCameraUtils \
-	truth-prebuilt
+	truth-prebuilt \
+	androidx.heifwriter_heifwriter \
+	android-support-test
 
 LOCAL_JNI_SHARED_LIBRARIES := \
 	libctscamera2_jni \
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/native-camera-jni.cpp b/tests/camera/libctscamera2jni/native-camera-jni.cpp
index 1a35a9f..c4e7424 100644
--- a/tests/camera/libctscamera2jni/native-camera-jni.cpp
+++ b/tests/camera/libctscamera2jni/native-camera-jni.cpp
@@ -273,6 +273,21 @@
             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));
         }
@@ -548,7 +563,57 @@
         }
         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;
+    }
+
   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 +738,22 @@
         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;
+    }
+
     camera_status_t updateOutput(JNIEnv* env, ACaptureSessionOutput *output) {
         if (mSession == nullptr) {
             ALOGE("Testcase cannot update output configuration session %p",
@@ -1282,6 +1363,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 +1391,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 +1432,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 +1461,9 @@
         }
 
         ACameraMetadata_free(chars);
+        ACameraMetadata_free(copy);
         chars = nullptr;
+        copy = nullptr;
     }
 
     pass = true;
@@ -1367,6 +1471,9 @@
     if (chars) {
         ACameraMetadata_free(chars);
     }
+    if (copy) {
+        ACameraMetadata_free(copy);
+    }
     ACameraManager_deleteCameraIdList(cameraIdList);
     ACameraManager_delete(mgr);
     ALOGI("%s %s", __FUNCTION__, pass ? "pass" : "failed");
@@ -2264,7 +2371,7 @@
 }
 
 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 +2379,7 @@
     int numCameras = 0;
     bool pass = false;
     PreviewTestCase testCase;
+    ACameraMetadata* chars = nullptr;
 
     const char* outPath = (jOutPath == nullptr) ? nullptr :
             env->GetStringUTFChars(jOutPath, nullptr);
@@ -2304,6 +2412,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 +2430,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 +2532,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 +2597,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 +2615,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 +2649,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 +2679,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 +2744,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 +2806,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..0b9f9c3 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);
@@ -547,14 +561,23 @@
             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 +585,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 +604,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 +632,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);
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 78f7523..42f2f58 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);
@@ -259,6 +288,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);
@@ -286,6 +322,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)) {
@@ -300,6 +347,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}
      */
@@ -385,9 +829,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        );
@@ -398,7 +839,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                  );
@@ -413,12 +853,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
@@ -488,41 +941,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) {
@@ -532,6 +998,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,
@@ -571,7 +1045,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);
@@ -614,22 +1087,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(
@@ -715,8 +1174,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]),
@@ -732,7 +1191,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));
             }
 
@@ -765,6 +1224,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;
@@ -808,6 +1270,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));
 
@@ -820,11 +1285,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);
@@ -891,32 +1362,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",
@@ -982,6 +1449,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++;
         }
@@ -1435,6 +1919,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(
@@ -1460,10 +1945,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 =
@@ -1509,12 +1990,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..6b95f8c 100644
--- a/tests/camera/src/android/hardware/camera2/cts/ImageReaderTest.java
+++ b/tests/camera/src/android/hardware/camera2/cts/ImageReaderTest.java
@@ -39,7 +39,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 +66,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 +140,18 @@
         }
     }
 
+    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 +233,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 +251,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 +295,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);
@@ -306,12 +316,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);
@@ -329,13 +339,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 +520,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);
@@ -901,7 +911,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,7 +1054,7 @@
             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 (VERBOSE) Log.v(TAG, "finish validation of image " + numImageVerified);
diff --git a/tests/camera/src/android/hardware/camera2/cts/ImageWriterTest.java b/tests/camera/src/android/hardware/camera2/cts/ImageWriterTest.java
index 65c8eb1..6216f36 100644
--- a/tests/camera/src/android/hardware/camera2/cts/ImageWriterTest.java
+++ b/tests/camera/src/android/hardware/camera2/cts/ImageWriterTest.java
@@ -28,7 +28,6 @@
 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 +44,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);
@@ -94,11 +92,11 @@
         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;
                 }
+                openDevice(id);
                 readerWriterFormatTestByCamera(ImageFormat.YUV_420_888);
             } finally {
                 closeDevice(id);
@@ -243,8 +241,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 +261,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 +278,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..b608f93 100644
--- a/tests/camera/src/android/hardware/camera2/cts/LogicalCameraDeviceTest.java
+++ b/tests/camera/src/android/hardware/camera2/cts/LogicalCameraDeviceTest.java
@@ -39,7 +39,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,12 +58,13 @@
 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 boolean VERBOSE = Log.isLoggable(TAG, Log.VERBOSE);
@@ -77,35 +77,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 +132,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 +173,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 +286,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 +408,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<>();
@@ -536,7 +535,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..3cac3b5 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);
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..f7f397e 100644
--- a/tests/camera/src/android/hardware/camera2/cts/PerformanceTest.java
+++ b/tests/camera/src/android/hardware/camera2/cts/PerformanceTest.java
@@ -18,6 +18,7 @@
 
 import static com.android.ex.camera2.blocking.BlockingSessionCallback.SESSION_CLOSED;
 
+import android.app.Instrumentation;
 import android.graphics.ImageFormat;
 import android.hardware.camera2.CameraAccessException;
 import android.hardware.camera2.CameraCaptureSession;
@@ -39,7 +40,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,11 +60,15 @@
 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 {
     private static final String TAG = "PerformanceTest";
     private static final String REPORT_LOG_NAME = "CtsCameraTestCases";
@@ -92,13 +97,16 @@
     private ImageWriter mWriter;
     private SimpleCaptureCallback mZslResultListener;
 
+    private Instrumentation mInstrumentation;
+
     @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();
     }
 
@@ -116,6 +124,7 @@
      * For depth-only devices, timing is done with the DEPTH16 format instead.
      * </p>
      */
+    @Test
     public void testCameraLaunch() throws Exception {
         double[] avgCameraLaunchTimes = new double[mCameraIds.length];
 
@@ -206,7 +215,7 @@
                 closeImageReader();
             }
             counter++;
-            mReportLog.submit(getInstrumentation());
+            mReportLog.submit(mInstrumentation);
 
             if (VERBOSE) {
                 Log.v(TAG, "Camera " + id + " device open times(ms): "
@@ -246,14 +255,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
@@ -263,27 +272,65 @@
      * out the capture request and getting the full capture result.
      * </p>
      */
+    @Test
     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,15 +346,21 @@
                             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();
@@ -327,10 +380,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) {
@@ -342,36 +400,47 @@
                     // simulate real scenario (preview runs a bit)
                     waitForNumResults(previewResultListener, NUM_RESULTS_WAIT);
 
-                    stopPreview();
+                    stopPreviewAndDrain();
 
+                    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();
+                closeImageReaders(readers);
+                readers = null;
                 closeDevice();
             }
             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);
         }
     }
 
@@ -384,6 +453,7 @@
      * gap between results.
      * </p>
      */
+    @Test
     public void testMultipleCapture() throws Exception {
         double[] avgResultTimes = new double[mCameraIds.length];
         double[] avgDurationMs = new double[mCameraIds.length];
@@ -445,13 +515,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
@@ -562,7 +631,7 @@
                 closeDevice();
             }
             counter++;
-            mReportLog.submit(getInstrumentation());
+            mReportLog.submit(mInstrumentation);
         }
 
         // Result will not be reported in CTS report if no summary is printed.
@@ -571,11 +640,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);
         }
     }
 
@@ -583,6 +652,7 @@
      * Test reprocessing shot-to-shot latency with default NR and edge options, i.e., from the time
      * a reprocess request is issued to the time the reprocess image is returned.
      */
+    @Test
     public void testReprocessingLatency() throws Exception {
         for (String id : mCameraIds) {
             for (int format : REPROCESS_FORMATS) {
@@ -601,7 +671,7 @@
                 } finally {
                     closeReaderWriters();
                     closeDevice();
-                    mReportLog.submit(getInstrumentation());
+                    mReportLog.submit(mInstrumentation);
                 }
             }
         }
@@ -612,6 +682,7 @@
      * during a given amount of time.
      *
      */
+    @Test
     public void testReprocessingThroughput() throws Exception {
         for (String id : mCameraIds) {
             for (int format : REPROCESS_FORMATS) {
@@ -630,7 +701,7 @@
                 } finally {
                     closeReaderWriters();
                     closeDevice();
-                    mReportLog.submit(getInstrumentation());
+                    mReportLog.submit(mInstrumentation);
                 }
             }
         }
@@ -640,6 +711,7 @@
      * Test reprocessing shot-to-shot latency with High Quality NR and edge options, i.e., from the
      * time a reprocess request is issued to the time the reprocess image is returned.
      */
+    @Test
     public void testHighQualityReprocessingLatency() throws Exception {
         for (String id : mCameraIds) {
             for (int format : REPROCESS_FORMATS) {
@@ -658,7 +730,7 @@
                 } finally {
                     closeReaderWriters();
                     closeDevice();
-                    mReportLog.submit(getInstrumentation());
+                    mReportLog.submit(mInstrumentation);
                 }
             }
         }
@@ -669,6 +741,7 @@
      * be reprocessed during a given amount of time.
      *
      */
+    @Test
     public void testHighQualityReprocessingThroughput() throws Exception {
         for (String id : mCameraIds) {
             for (int format : REPROCESS_FORMATS) {
@@ -687,7 +760,7 @@
                 } finally {
                     closeReaderWriters();
                     closeDevice();
-                    mReportLog.submit(getInstrumentation());
+                    mReportLog.submit(mInstrumentation);
                 }
             }
         }
@@ -696,6 +769,7 @@
     /**
      * Testing reprocessing caused preview stall (frame drops)
      */
+    @Test
     public void testReprocessingCaptureStall() throws Exception {
         for (String id : mCameraIds) {
             for (int format : REPROCESS_FORMATS) {
@@ -713,7 +787,7 @@
                 } finally {
                     closeReaderWriters();
                     closeDevice();
-                    mReportLog.submit(getInstrumentation());
+                    mReportLog.submit(mInstrumentation);
                 }
             }
         }
diff --git a/tests/camera/src/android/hardware/camera2/cts/RecordingTest.java b/tests/camera/src/android/hardware/camera2/cts/RecordingTest.java
index 651b8b6..e98229d 100644
--- a/tests/camera/src/android/hardware/camera2/cts/RecordingTest.java
+++ b/tests/camera/src/android/hardware/camera2/cts/RecordingTest.java
@@ -22,7 +22,9 @@
 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.util.Size;
 import android.hardware.camera2.cts.testcases.Camera2SurfaceViewTestCase;
@@ -39,7 +41,6 @@
 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 +51,8 @@
 
 import junit.framework.AssertionFailedError;
 
+import org.junit.Test;
+
 import java.io.File;
 import java.util.ArrayList;
 import java.util.Arrays;
@@ -61,7 +64,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 +80,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,
@@ -104,12 +105,12 @@
     private long mRecordingStartTime;
 
     @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();
     }
 
@@ -117,29 +118,30 @@
         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);
@@ -163,6 +165,7 @@
      * recorded video. Preview is set to the video size.
      * </p>
      */
+    @Test
     public void testBasicVideoStabilizationRecording() throws Exception {
         doBasicRecording(/*useVideoStab*/true);
     }
@@ -179,6 +182,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 testBasicRecording() throws Exception {
         doBasicRecording(/*useVideoStab*/false);
     }
@@ -192,6 +196,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 +222,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 +251,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 +266,7 @@
      * validated according to the recording configuration.
      * </p>
      */
+    @Test
     public void testMediaCodecRecording() throws Exception {
         // TODO. Need implement.
     }
@@ -274,6 +283,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 +300,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 +308,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 +365,7 @@
                     continue;
                 }
 
-                mOutMediaFileName = VIDEO_FILE_PATH + "/test_video.mp4";
+                mOutMediaFileName = mDebugFileNameBase + "/test_video.mp4";
                 prepareRecording(size, videoFramerate, captureRate);
                 updatePreviewSurfaceWithVideo(size, captureRate);
 
@@ -428,23 +445,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 +500,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 +542,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 +623,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 +658,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 +698,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 +735,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 +841,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).
@@ -942,9 +971,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";
             }
 
@@ -996,9 +1025,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";
             }
 
@@ -1074,23 +1103,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 +1289,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";
             }
 
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/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..1169e64 100644
--- a/tests/camera/src/android/hardware/camera2/cts/testcases/Camera2AndroidTestCase.java
+++ b/tests/camera/src/android/hardware/camera2/cts/testcases/Camera2AndroidTestCase.java
@@ -24,6 +24,7 @@
 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;
@@ -36,7 +37,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 +57,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 +77,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 +112,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 +223,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,
@@ -394,6 +406,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..fb0517b 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();
         }
     }
 
@@ -583,6 +599,74 @@
     }
 
     /**
+     * Close the pending images then close current active {@link ImageReader} objects.
+     */
+    protected void closeImageReaders(ImageReader[] readers) {
+        if ((readers != null) && (readers.length > 0)) {
+            for (ImageReader reader : readers) {
+                CameraTestUtils.closeImageReader(reader);
+            }
+        }
+    }
+
+    /**
+     * 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 +720,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 +747,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 +765,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
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..5fbb04e 100644
--- a/tests/camera/src/android/hardware/cts/CameraTest.java
+++ b/tests/camera/src/android/hardware/cts/CameraTest.java
@@ -16,6 +16,8 @@
 
 package android.hardware.cts;
 
+import android.app.Activity;
+import android.app.Instrumentation;
 import android.content.pm.PackageManager;
 import android.graphics.BitmapFactory;
 import android.graphics.ImageFormat;
@@ -36,17 +38,21 @@
 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.support.test.InstrumentationRegistry;
 import android.test.MoreAsserts;
 import android.test.UiThreadTest;
 import android.test.suitebuilder.annotation.LargeTest;
 import android.util.Log;
 import android.view.SurfaceHolder;
 
+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.File;
 import java.io.FileOutputStream;
 import java.io.IOException;
@@ -60,19 +66,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;
@@ -122,28 +133,28 @@
     private final ConditionVariable mFocusDone = new ConditionVariable();
     private final ConditionVariable mSnapshotDone = new ConditionVariable();
 
+    private static final String REPORT_LOG_NAME = "CtsCamera1TestCases";
+    private DeviceReportLog mReportLog;
+    private Instrumentation mInstrumentation;
+
     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 {
+        mInstrumentation = InstrumentationRegistry.getInstrumentation();
     }
 
-    @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 +174,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 +198,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 +318,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 +413,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 +459,7 @@
     }
 
     @UiThreadTest
+    @Test
     public void testPreviewCallback() throws Exception {
         int nCameras = Camera.getNumberOfCameras();
         for (int id = 0; id < nCameras; id++) {
@@ -490,6 +504,7 @@
     }
 
     @UiThreadTest
+    @Test
     public void testStabilizationOneShotPreviewCallback() throws Exception {
         int nCameras = Camera.getNumberOfCameras();
         for (int id = 0; id < nCameras; id++) {
@@ -514,6 +529,7 @@
     }
 
     @UiThreadTest
+    @Test
     public void testSetOneShotPreviewCallback() throws Exception {
         int nCameras = Camera.getNumberOfCameras();
         for (int id = 0; id < nCameras; id++) {
@@ -537,6 +553,7 @@
     }
 
     @UiThreadTest
+    @Test
     public void testSetPreviewDisplay() throws Exception {
         int nCameras = Camera.getNumberOfCameras();
         for (int id = 0; id < nCameras; id++) {
@@ -546,7 +563,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 +597,147 @@
     }
 
     @UiThreadTest
+    @Test
+    public void testPerformance() throws Exception {
+        final int NUM_TEST_LOOPS = 10;
+
+        int nCameras = Camera.getNumberOfCameras();
+        double[] avgCameraTakePictureTimes = new double[nCameras];
+
+        for (int id = 0; id < nCameras; id++) {
+            String streamName = "test_camera_takePicture";
+            mReportLog = new DeviceReportLog(REPORT_LOG_NAME, streamName);
+            mReportLog.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++) {
+                SurfaceHolder holder = mActivityRule.getActivity().getSurfaceView().getHolder();
+                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);
+                }
+
+                mCamera.setPreviewDisplay(holder);
+                startPreviewTimeMs = SystemClock.elapsedRealtime();
+                blockingStartPreview();
+                startPreviewTimes[i] = SystemClock.elapsedRealtime() - startPreviewTimeMs;
+
+                if (afSupported) {
+                    autofocusTimeMs = SystemClock.elapsedRealtime();
+                    mCamera.autoFocus(mAutoFocusCallback);
+                    waitForFocusDone();
+                    cameraAutoFocusTimes[i] = SystemClock.elapsedRealtime() - autofocusTimeMs;
+                }
+
+                //Let preview run for a while
+                Thread.sleep(1000);
+
+                takePictureTimeMs = SystemClock.elapsedRealtime();
+                mCamera.takePicture(/*shutterCallback*/ null, /*rawPictureCallback*/ null,
+                        mJpegPictureCallback);
+                waitForSnapshotDone();
+                cameraTakePictureTimes[i] = SystemClock.elapsedRealtime() - takePictureTimeMs;
+
+                //Resume preview after image capture
+                blockingStartPreview();
+
+                stopPreviewTimeMs = SystemClock.elapsedRealtime();
+                mCamera.stopPreview();
+                closeTimeMs = SystemClock.elapsedRealtime();
+                stopPreviewTimes[i] = closeTimeMs - stopPreviewTimeMs;
+
+                terminateMessageLooper();
+                cameraCloseTimes[i] = SystemClock.elapsedRealtime() - closeTimeMs;
+            }
+
+            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);
+            mReportLog.addValues("camera_open_time", cameraOpenTimes, ResultType.LOWER_BETTER,
+                    ResultUnit.MS);
+            mReportLog.addValues("camera_start_preview_time", startPreviewTimes,
+                    ResultType.LOWER_BETTER, ResultUnit.MS);
+            if (afSupported) {
+                mReportLog.addValues("camera_autofocus_time", cameraAutoFocusTimes,
+                        ResultType.LOWER_BETTER, ResultUnit.MS);
+            }
+            mReportLog.addValues("camera_stop_preview", stopPreviewTimes,
+                    ResultType.LOWER_BETTER, ResultUnit.MS);
+            mReportLog.addValues("camera_close_time", cameraCloseTimes,
+                    ResultType.LOWER_BETTER, ResultUnit.MS);
+            mReportLog.addValues("camera_takepicture_time", cameraTakePictureTimes,
+                    ResultType.LOWER_BETTER, ResultUnit.MS);
+
+            mReportLog.submit(mInstrumentation);
+        }
+
+        if (nCameras != 0) {
+            String streamName = "test_camera_takepicture_average";
+            mReportLog = new DeviceReportLog(REPORT_LOG_NAME, streamName);
+            mReportLog.setSummary("camera_takepicture_average_time_for_all_cameras",
+                    Stat.getAverage(avgCameraTakePictureTimes), ResultType.LOWER_BETTER,
+                    ResultUnit.MS);
+            mReportLog.submit(mInstrumentation);
+        }
+    }
+
+    @UiThreadTest
+    @Test
     public void testDisplayOrientation() throws Exception {
         int nCameras = Camera.getNumberOfCameras();
         for (int id = 0; id < nCameras; id++) {
@@ -618,6 +776,7 @@
     }
 
     @UiThreadTest
+    @Test
     public void testParameters() throws Exception {
         int nCameras = Camera.getNumberOfCameras();
         for (int id = 0; id < nCameras; id++) {
@@ -841,6 +1000,7 @@
     }
 
     @UiThreadTest
+    @Test
     public void testJpegThumbnailSize() throws Exception {
         int nCameras = Camera.getNumberOfCameras();
         for (int id = 0; id < nCameras; id++) {
@@ -868,7 +1028,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 +1054,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 +1070,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 +1096,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 +1138,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 +1321,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 +1367,7 @@
     }
 
     @UiThreadTest
+    @Test
     public void testLockUnlock() throws Exception {
         int nCameras = Camera.getNumberOfCameras();
         for (int id = 0; id < nCameras; id++) {
@@ -1218,7 +1380,7 @@
         initializeMessageLooper(cameraId);
         Camera.Parameters parameters = mCamera.getParameters();
         SurfaceHolder surfaceHolder;
-        surfaceHolder = getActivity().getSurfaceView().getHolder();
+        surfaceHolder = mActivityRule.getActivity().getSurfaceView().getHolder();
         CamcorderProfile profile = CamcorderProfile.get(cameraId,
                 CamcorderProfile.QUALITY_LOW);
         Camera.Size videoSize = null; // Used for external camera
@@ -1398,6 +1560,7 @@
     }
 
     @UiThreadTest
+    @Test
     public void testPreviewCallbackWithBuffer() throws Exception {
         int nCameras = Camera.getNumberOfCameras();
         for (int id = 0; id < nCameras; id++) {
@@ -1409,7 +1572,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 +1649,7 @@
     }
 
     @UiThreadTest
+    @Test
     public void testImmediateZoom() throws Exception {
         int nCameras = Camera.getNumberOfCameras();
         for (int id = 0; id < nCameras; id++) {
@@ -1548,6 +1712,7 @@
     }
 
     @UiThreadTest
+    @Test
     public void testSmoothZoom() throws Exception {
         int nCameras = Camera.getNumberOfCameras();
         for (int id = 0; id < nCameras; id++) {
@@ -1673,6 +1838,7 @@
     }
 
     @UiThreadTest
+    @Test
     public void testFocusDistances() throws Exception {
         int nCameras = Camera.getNumberOfCameras();
         for (int id = 0; id < nCameras; id++) {
@@ -1781,6 +1947,7 @@
     }
 
     @UiThreadTest
+    @Test
     public void testCancelAutofocus() throws Exception {
         int nCameras = Camera.getNumberOfCameras();
         for (int id = 0; id < nCameras; id++) {
@@ -1837,7 +2004,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 +2029,7 @@
     }
 
     @UiThreadTest
+    @Test
     public void testMultipleCameras() throws Exception {
         int nCameras = Camera.getNumberOfCameras();
         Log.v(TAG, "total " + nCameras + " cameras");
@@ -1924,6 +2092,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 +2172,7 @@
     }
 
     @UiThreadTest
+    @Test
     public void testPreviewFpsRange() throws Exception {
         int nCameras = Camera.getNumberOfCameras();
         for (int id = 0; id < nCameras; id++) {
@@ -2231,6 +2401,7 @@
     }
 
     @UiThreadTest
+    @Test
     public void testSceneMode() throws Exception {
         int nCameras = Camera.getNumberOfCameras();
         for (int id = 0; id < nCameras; id++) {
@@ -2331,6 +2502,7 @@
     }
 
     @UiThreadTest
+    @Test
     public void testInvalidParameters() throws Exception {
         int nCameras = Camera.getNumberOfCameras();
         for (int id = 0; id < nCameras; id++) {
@@ -2385,6 +2557,7 @@
     }
 
     @UiThreadTest
+    @Test
     public void testGetParameterDuringFocus() throws Exception {
         int nCameras = Camera.getNumberOfCameras();
         for (int id = 0; id < nCameras; id++) {
@@ -2420,6 +2593,7 @@
     }
 
     @UiThreadTest
+    @Test
     public void testPreviewFormats() throws Exception {
         int nCameras = Camera.getNumberOfCameras();
         for (int id = 0; id < nCameras; id++) {
@@ -2444,6 +2618,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 +2707,7 @@
     }
 
     @UiThreadTest
+    @Test
     public void testFocusAreas() throws Exception {
         int nCameras = Camera.getNumberOfCameras();
         for (int id = 0; id < nCameras; id++) {
@@ -2551,6 +2727,7 @@
     }
 
     @UiThreadTest
+    @Test
     public void testMeteringAreas() throws Exception {
         int nCameras = Camera.getNumberOfCameras();
         for (int id = 0; id < nCameras; id++) {
@@ -2687,6 +2864,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 +2894,7 @@
     }
 
     @UiThreadTest
+    @Test
     public void testRecordingHint() throws Exception {
         int nCameras = Camera.getNumberOfCameras();
         for (int id = 0; id < nCameras; id++) {
@@ -2728,7 +2907,7 @@
         initializeMessageLooper(cameraId);
         Parameters parameters = mCamera.getParameters();
 
-        SurfaceHolder holder = getActivity().getSurfaceView().getHolder();
+        SurfaceHolder holder = mActivityRule.getActivity().getSurfaceView().getHolder();
         CamcorderProfile profile = CamcorderProfile.get(cameraId,
                 CamcorderProfile.QUALITY_LOW);
         Camera.Size videoSize = null; // for external camera
@@ -2811,6 +2990,7 @@
     }
 
     @UiThreadTest
+    @Test
     public void testAutoExposureLock() throws Exception {
         int nCameras = Camera.getNumberOfCameras();
         for (int id = 0; id < nCameras; id++) {
@@ -2827,6 +3007,7 @@
     }
 
     @UiThreadTest
+    @Test
     public void testAutoWhiteBalanceLock() throws Exception {
         int nCameras = Camera.getNumberOfCameras();
         for (int id = 0; id < nCameras; id++) {
@@ -2843,6 +3024,7 @@
     }
 
     @UiThreadTest
+    @Test
     public void test3ALockInteraction() throws Exception {
         int nCameras = Camera.getNumberOfCameras();
         for (int id = 0; id < nCameras; id++) {
@@ -3067,6 +3249,7 @@
     }
 
     @UiThreadTest
+    @Test
     public void testFaceDetection() throws Exception {
         int nCameras = Camera.getNumberOfCameras();
         for (int id = 0; id < nCameras; id++) {
@@ -3193,6 +3376,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 +3405,7 @@
             return;
         }
 
-        SurfaceHolder holder = getActivity().getSurfaceView().getHolder();
+        SurfaceHolder holder = mActivityRule.getActivity().getSurfaceView().getHolder();
 
         for (int profileId: mCamcorderProfileList) {
             if (!CamcorderProfile.hasProfile(cameraId, profileId)) {
@@ -3271,6 +3455,7 @@
         }
     }
 
+    @Test
     public void testPreviewCallbackWithPicture() throws Exception {
         int nCameras = Camera.getNumberOfCameras();
         for (int id = 0; id < nCameras; id++) {
@@ -3330,6 +3515,7 @@
         terminateMessageLooper();
     }
 
+    @Test
     public void testEnableShutterSound() throws Exception {
         int nCameras = Camera.getNumberOfCameras();
         for (int id = 0; id < nCameras; id++) {
@@ -3356,8 +3542,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/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/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 f0714e1..9f12cb7 100644
--- a/tests/camera/utils/src/android/hardware/camera2/cts/CameraTestUtils.java
+++ b/tests/camera/utils/src/android/hardware/camera2/cts/CameraTestUtils.java
@@ -47,7 +47,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 +125,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);
@@ -1112,7 +1108,8 @@
             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.Y8:
+                assertEquals("JPEG/RAW/depth/Y8 Images should have one plane", 1, planes.length);
                 break;
             default:
                 fail("Unsupported Image Format: " + format);
@@ -1535,6 +1532,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 +1645,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 +2065,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 +2503,48 @@
             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);
+    }
 }
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..5085bcc 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() {
diff --git a/tests/contentcaptureservice/Android.mk b/tests/contentcaptureservice/Android.mk
new file mode 100644
index 0000000..48254ef
--- /dev/null
+++ b/tests/contentcaptureservice/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)
+
+# 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 := 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..abc87bf
--- /dev/null
+++ b/tests/contentcaptureservice/AndroidManifest.xml
@@ -0,0 +1,65 @@
+<?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=".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>
+
+        <service
+            android:name=".CtsSmartSuggestionsService"
+            android:label="CtsSmartSuggestionsService"
+            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..782a5b1
--- /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/119638958): 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/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..a50141f
--- /dev/null
+++ b/tests/contentcaptureservice/src/android/contentcaptureservice/cts/AbstractContentCaptureActivity.java
@@ -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 android.contentcaptureservice.cts;
+
+import android.app.Activity;
+
+/**
+ * Base class for all activities.
+ */
+abstract class AbstractContentCaptureActivity extends Activity {
+
+}
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..a8866c5
--- /dev/null
+++ b/tests/contentcaptureservice/src/android/contentcaptureservice/cts/AbstractContentCaptureIntegrationTest.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.contentcaptureservice.cts;
+
+import static android.contentcaptureservice.cts.Helper.GENERIC_TIMEOUT_MS;
+import static android.contentcaptureservice.cts.Helper.TAG;
+import static android.contentcaptureservice.cts.Helper.resetService;
+
+import android.app.Application;
+import android.content.Context;
+import android.content.Intent;
+import android.contentcaptureservice.cts.common.ActivitiesWatcher;
+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 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> {
+
+    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(TAG);
+
+    protected final SafeCleanerRule mSafeCleanerRule = new SafeCleanerRule()
+            .setDumper(mLoggingRule)
+            .add(() -> {
+                return CtsSmartSuggestionsService.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());
+
+
+    protected AbstractContentCaptureIntegrationTest(@NonNull Class<A> activityClass) {
+        mActivityClass = activityClass;
+    }
+
+    @Before
+    public void registerLifecycleCallback() {
+        Log.d(TAG, "Registering lifecycle callback");
+        final Application app = (Application) sContext.getApplicationContext();
+        mActivitiesWatcher = new ActivitiesWatcher(GENERIC_TIMEOUT_MS);
+        app.registerActivityLifecycleCallbacks(mActivitiesWatcher);
+    }
+
+    @After
+    public void unregisterLifecycleCallback() {
+        if (mActivitiesWatcher != null) {
+            Log.d(TAG, "Unregistering lifecycle callback");
+            final Application app = (Application) sContext.getApplicationContext();
+            app.unregisterActivityLifecycleCallbacks(mActivitiesWatcher);
+        }
+    }
+
+    @After
+    public void restoreDefaultService() {
+        resetService();
+    }
+
+    /**
+     * 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(TAG, "Launching " + mActivityClass.getSimpleName());
+
+        return getActivityTestRule().launchActivity(new Intent(sContext, mActivityClass));
+    }
+}
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..8b7601b
--- /dev/null
+++ b/tests/contentcaptureservice/src/android/contentcaptureservice/cts/Assertions.java
@@ -0,0 +1,108 @@
+/*
+ * Copyright (C) 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR 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.view.contentcapture.ContentCaptureEvent.TYPE_VIEW_APPEARED;
+import static android.view.contentcapture.ContentCaptureEvent.TYPE_VIEW_DISAPPEARED;
+
+import static com.google.common.truth.Truth.assertThat;
+import static com.google.common.truth.Truth.assertWithMessage;
+
+import android.app.Activity;
+import android.contentcaptureservice.cts.CtsSmartSuggestionsService.Session;
+import android.view.View;
+import android.view.autofill.AutofillId;
+import android.view.contentcapture.ContentCaptureEvent;
+import android.view.contentcapture.ViewNode;
+import android.widget.TextView;
+
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+
+/**
+ * Helper for common assertions.
+ */
+final class Assertions {
+
+    /**
+     * Asserts a session belongs to the right activity.
+     */
+    public static void assertRightActivity(@NonNull Session session, @NonNull Activity activity) {
+        assertWithMessage("wrong activity for %s", session)
+                .that(session.context.getActivityComponent())
+                .isEqualTo(activity.getComponentName());
+    }
+
+    /**
+     * Asserts an activity lifecycle event.
+     *
+     * @deprecated most like activity lifecycle events will go away.
+     */
+    @Deprecated
+    public static void assertLifecycleEvent(@NonNull ContentCaptureEvent event, int expected) {
+        assertWithMessage("wrong event: %s", event).that(event.getType()).isEqualTo(expected);
+    }
+
+    /**
+     * Asserts the contents of a {@link #TYPE_VIEW_APPEARED} event.
+     */
+    public static void assertViewAppeared(@NonNull ContentCaptureEvent event,
+            @NonNull View expectedView, @Nullable AutofillId expectedParentId) {
+        assertWithMessage("wrong event: %s", event).that(event.getType())
+                .isEqualTo(TYPE_VIEW_APPEARED);
+        final ViewNode node = event.getViewNode();
+        assertThat(node).isNotNull();
+        assertWithMessage("invalid time on %s", event).that(event.getEventTime())
+                .isAtLeast(MY_EPOCH);
+        assertWithMessage("wrong class on %s", node).that(node.getClassName())
+                .isEqualTo(expectedView.getClass().getName());
+        assertWithMessage("wrong autofill id on %s", node).that(node.getAutofillId())
+                .isEqualTo(expectedView.getAutofillId());
+        assertWithMessage("wrong parent autofill id on %s", node).that(node.getParentAutofillId())
+                .isEqualTo(expectedParentId);
+        if (expectedView instanceof TextView) {
+            assertWithMessage("wrong text id on %s", node).that(node.getText().toString())
+                    .isEqualTo(((TextView) expectedView).getText().toString());
+        }
+        // TODO(b/119638958): test more fields, like resource id
+    }
+
+    /**
+     * Asserts the contents of a {@link #TYPE_VIEW_DISAPPEARED} event.
+     */
+    public static void assertViewDisappeared(@NonNull ContentCaptureEvent event,
+            @NonNull AutofillId expectedId) {
+        assertWithMessage("wrong event: %s", event).that(event.getType())
+                .isEqualTo(TYPE_VIEW_DISAPPEARED);
+        assertWithMessage("invalid time on %s", event).that(event.getEventTime())
+            .isAtLeast(MY_EPOCH);
+        assertWithMessage("event %s should not have a ViewNode", event).that(event.getViewNode())
+                .isNull();
+        assertWithMessage("event %s should not have text", event).that(event.getText())
+            .isNull();
+        assertWithMessage("event %s should not have flags", event).that(event.getFlags())
+            .isEqualTo(0);
+        assertWithMessage("event %s should not have a ViewNode", event).that(event.getViewNode())
+        .isNull();
+        assertWithMessage("wrong autofillId on event %s", event).that(event.getId())
+                .isEqualTo(expectedId);
+    }
+
+    private Assertions() {
+        throw new UnsupportedOperationException("contain static methods only");
+    }
+}
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..6fd9c2a
--- /dev/null
+++ b/tests/contentcaptureservice/src/android/contentcaptureservice/cts/BlankActivity.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.contentcaptureservice.cts;
+
+public class BlankActivity extends AbstractContentCaptureActivity {
+
+}
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..7714b14
--- /dev/null
+++ b/tests/contentcaptureservice/src/android/contentcaptureservice/cts/BlankActivityTest.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 android.contentcaptureservice.cts;
+
+import static android.contentcaptureservice.cts.Assertions.assertLifecycleEvent;
+import static android.contentcaptureservice.cts.Assertions.assertRightActivity;
+import static android.contentcaptureservice.cts.Helper.TAG;
+import static android.contentcaptureservice.cts.Helper.enableService;
+import static android.contentcaptureservice.cts.common.ActivitiesWatcher.ActivityLifecycle.DESTROYED;
+import static android.contentcaptureservice.cts.common.ActivitiesWatcher.ActivityLifecycle.RESUMED;
+import static android.view.contentcapture.ContentCaptureEvent.TYPE_ACTIVITY_PAUSED;
+import static android.view.contentcapture.ContentCaptureEvent.TYPE_ACTIVITY_RESUMED;
+import static android.view.contentcapture.ContentCaptureEvent.TYPE_ACTIVITY_STARTED;
+import static android.view.contentcapture.ContentCaptureEvent.TYPE_ACTIVITY_STOPPED;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import android.contentcaptureservice.cts.CtsSmartSuggestionsService.Session;
+import android.contentcaptureservice.cts.common.ActivitiesWatcher.ActivityWatcher;
+import android.support.test.rule.ActivityTestRule;
+import android.util.Log;
+import android.view.contentcapture.ContentCaptureEvent;
+
+import org.junit.Test;
+
+import java.util.List;
+
+public class BlankActivityTest extends AbstractContentCaptureIntegrationTest<BlankActivity> {
+
+    private static final ActivityTestRule<BlankActivity> sActivityRule = new ActivityTestRule<>(
+            BlankActivity.class, false, false);
+
+    public BlankActivityTest() {
+        super(BlankActivity.class);
+    }
+
+    @Override
+    protected ActivityTestRule<BlankActivity> getActivityTestRule() {
+        return sActivityRule;
+    }
+
+    // TODO(b/119638958): rename once we add moar tests
+    @Test
+    public void testIt() throws Exception {
+        enableService();
+
+        // TODO(b/119638958): move to super class
+        final ActivityWatcher watcher = mActivitiesWatcher.watch(BlankActivity.class);
+
+        final BlankActivity activity = launchActivity();
+        watcher.waitFor(RESUMED);
+
+        activity.finish();
+        watcher.waitFor(DESTROYED);
+
+        final CtsSmartSuggestionsService service = CtsSmartSuggestionsService.getInstance();
+        final Session session = service.getFinishedSession(BlankActivity.class);
+
+        assertRightActivity(session, activity);
+
+        final List<ContentCaptureEvent> events = session.getEvents();
+        Log.v(TAG, "events: " + events);
+        assertThat(events).hasSize(4);
+        assertLifecycleEvent(events.get(0), TYPE_ACTIVITY_STARTED);
+        assertLifecycleEvent(events.get(1), TYPE_ACTIVITY_RESUMED);
+        assertLifecycleEvent(events.get(2), TYPE_ACTIVITY_PAUSED);
+        assertLifecycleEvent(events.get(3), TYPE_ACTIVITY_STOPPED);
+    }
+}
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..b0dca6f
--- /dev/null
+++ b/tests/contentcaptureservice/src/android/contentcaptureservice/cts/ContentCaptureLoggingTestRule.java
@@ -0,0 +1,91 @@
+/*
+ * 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.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/119638958): 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/119638958): 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/119638958, 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 autofillDump = runShellCommand("dumpsys content_capture");
+        Log.e(mTag, "content_capture dump: \n" + autofillDump);
+        final String activityDump = runShellCommand("dumpsys activity top --contentcapture");
+        Log.e(mTag, "top activity dump: \n" + activityDump);
+        mDumped = true;
+    }
+}
diff --git a/tests/contentcaptureservice/src/android/contentcaptureservice/cts/CtsSmartSuggestionsService.java b/tests/contentcaptureservice/src/android/contentcaptureservice/cts/CtsSmartSuggestionsService.java
new file mode 100644
index 0000000..ca5b9cf
--- /dev/null
+++ b/tests/contentcaptureservice/src/android/contentcaptureservice/cts/CtsSmartSuggestionsService.java
@@ -0,0 +1,236 @@
+/*
+ * Copyright (C) 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR 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 android.app.Activity;
+import android.service.contentcapture.ContentCaptureEventsRequest;
+import android.service.contentcapture.ContentCaptureService;
+import android.service.contentcapture.InteractionContext;
+import android.service.contentcapture.InteractionSessionId;
+import android.util.ArrayMap;
+import android.util.Log;
+import android.view.contentcapture.ContentCaptureEvent;
+import android.view.contentcapture.ViewNode;
+
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+import java.util.concurrent.CountDownLatch;
+
+// TODO(b/119638958): 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 CtsSmartSuggestionsService extends ContentCaptureService {
+
+    private static final String TAG = CtsSmartSuggestionsService.class.getSimpleName();
+
+    public static final String SERVICE_NAME = MY_PACKAGE + "/."
+            + CtsSmartSuggestionsService.class.getSimpleName();
+
+    private static final CountDownLatch sInstanceLatch = new CountDownLatch(1);
+
+    private static CtsSmartSuggestionsService sInstance;
+
+    // TODO(b/119638958): add method to clear static state / call it from @Before
+    private static final ArrayList<Throwable> sExceptions = new ArrayList<>();
+
+    public static CtsSmartSuggestionsService getInstance() throws InterruptedException {
+        await(sInstanceLatch, "Service not started");
+        return sInstance;
+    }
+
+    private final ArrayMap<InteractionSessionId, Session> mOpenSessions = new ArrayMap<>();
+    private final ArrayMap<String, Session> mFinishedSessions = new ArrayMap<>();
+    private final ArrayMap<String, CountDownLatch> mUnfinishedSessionLatches = new ArrayMap<>();
+
+    @Override
+    public void onCreate() {
+        Log.d(TAG, "onCreate(): sInstance=" + sInstance);
+        super.onCreate();
+
+        if (sInstance == null) {
+            sInstance = this;
+            sInstanceLatch.countDown();
+        } else {
+            Log.e(TAG, "onCreate(): already created:" + sInstance);
+            sExceptions.add(new IllegalStateException("onCreate() again"));
+        }
+    }
+
+    @Override
+    public void onDestroy() {
+        Log.d(TAG, "onDestroy(): sInstance=" + sInstance);
+        super.onDestroy();
+
+        if (this == sInstance) {
+            sInstance = null;
+        }
+    }
+
+    @Override
+    public void onCreateInteractionSession(InteractionContext context,
+            InteractionSessionId sessionId) {
+        Log.d(TAG, "onCreateInteractionSession(ctx=" + context + ", id=" + sessionId + ")");
+
+        safeRun(() -> {
+            final Session session = mOpenSessions.get(sessionId);
+            if (session != null) {
+                throw new IllegalStateException("Already contains session for " + sessionId
+                        + ": " + session);
+            }
+            mUnfinishedSessionLatches.put(context.getActivityComponent().getClassName(),
+                    new CountDownLatch(1));
+            mOpenSessions.put(sessionId, new Session(sessionId, context));
+        });
+    }
+
+    @Override
+    public void onDestroyInteractionSession(InteractionSessionId sessionId) {
+        Log.d(TAG, "onDestroyInteractionSession(" + sessionId + ")");
+        safeRun(() -> {
+            final Session session = getExistingSession(sessionId);
+            session.finished = true;
+            mOpenSessions.remove(sessionId);
+            final String className = session.context.getActivityComponent().getClassName();
+            if (mFinishedSessions.containsKey(className)) {
+                throw new IllegalStateException("Already destroyed " + className);
+            } else {
+                mFinishedSessions.put(className, session);
+                final CountDownLatch latch = getUnfinishedSessionLatch(className);
+                latch.countDown();
+            }
+        });
+    }
+
+    @Override
+    public void onContentCaptureEventsRequest(InteractionSessionId sessionId,
+            ContentCaptureEventsRequest request) {
+        final List<ContentCaptureEvent> events = request.getEvents();
+        final int size = events.size();
+        Log.d(TAG, "onContentCaptureEventsRequest(" + sessionId + "): " + size + " events");
+        for (int i = 0; i < size; i++) {
+            final ContentCaptureEvent event = events.get(i);
+            final StringBuilder msg = new StringBuilder("  ").append(i).append(": ").append(event);
+            final ViewNode node = event.getViewNode();
+            if (node != null) {
+                msg.append(", parent=").append(node.getParentAutofillId());
+            }
+            Log.v(TAG, msg.toString());
+        }
+        safeRun(() -> {
+            final Session session = getExistingSession(sessionId);
+            session.mRequests.add(request);
+        });
+    }
+
+    /**
+     * Gets the finished session for the given activity.
+     *
+     * @throws IllegalStateException if the session didn't finish yet.
+     */
+    @NonNull
+    public Session getFinishedSession(@NonNull Class<? extends Activity> clazz)
+            throws InterruptedException {
+        final String className = clazz.getName();
+        final CountDownLatch latch = getUnfinishedSessionLatch(className);
+        await(latch, "session for %s not finished yet", className);
+
+        final Session session = mFinishedSessions.get(className);
+        if (session == null) {
+            throwIllegalSessionStateException("No finished session for %s", className);
+        }
+        return session;
+    }
+
+    @NonNull
+    private CountDownLatch getUnfinishedSessionLatch(final String className) {
+        final CountDownLatch latch = mUnfinishedSessionLatches.get(className);
+        if (latch == null) {
+            throwIllegalSessionStateException("no latch for %s", className);
+        }
+        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)
+                + ". Open=" + mOpenSessions
+                + ". Latches=" + mUnfinishedSessionLatches
+                + ". Finished=" + mFinishedSessions);
+    }
+
+    private Session getExistingSession(@NonNull InteractionSessionId 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);
+        }
+    }
+
+    public final class Session {
+        public final InteractionSessionId id;
+        public final InteractionContext context;
+        private final List<ContentCaptureEventsRequest> mRequests = new ArrayList<>();
+        public boolean finished;
+
+        private Session(InteractionSessionId id, InteractionContext context) {
+            this.id = id;
+            this.context = context;
+        }
+
+        // TODO(b/119638958): 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() {
+            final List<ContentCaptureEvent> events = new ArrayList<>();
+            for (ContentCaptureEventsRequest request : mRequests) {
+                events.addAll(request.getEvents());
+            }
+            return Collections.unmodifiableList(events);
+        }
+
+        @Override
+        public String toString() {
+            return "[id=" + id + ", context=" + context + ", requests=" + mRequests.size()
+                    + ", finished=" + finished + "]";
+        }
+    }
+}
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..a640e36
--- /dev/null
+++ b/tests/contentcaptureservice/src/android/contentcaptureservice/cts/Helper.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.contentcaptureservice.cts;
+
+import static android.contentcaptureservice.cts.common.ShellHelper.runShellCommand;
+
+import android.os.SystemClock;
+import android.util.Log;
+
+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 = 2000;
+
+    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/119638958): use @TestingAPI for max duration constant
+        runShellCommand("cmd content_capture set temporary-service 0 " + service + " 12000");
+        // TODO(b/119638958): add a more robust mechanism to wait for service to be set.
+        // For example, when the service is set using a shell cmd, block until the
+        // IntelligencePerUserService is cached (or use a @TestingApi instead of shell cmd)
+        SystemClock.sleep(GENERIC_TIMEOUT_MS);
+    }
+
+    /**
+     * 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");
+    }
+
+    /**
+     * Sets {@link CtsSmartSuggestionsService} as the service for the current user.
+     */
+    public static void enableService() {
+        setService(CtsSmartSuggestionsService.SERVICE_NAME);
+    }
+
+    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..4991e2b
--- /dev/null
+++ b/tests/contentcaptureservice/src/android/contentcaptureservice/cts/LoginActivity.java
@@ -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.
+ */
+package android.contentcaptureservice.cts;
+
+import android.os.Bundle;
+import android.widget.EditText;
+import android.widget.LinearLayout;
+import android.widget.TextView;
+
+public class LoginActivity extends AbstractContentCaptureActivity {
+
+    LinearLayout mRootView;
+    TextView mUsernameLabel;
+    EditText mUsername;
+    TextView mPasswordLabel;
+    EditText mPassword;
+
+    @Override
+    protected void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+        setContentView(R.layout.login_activity);
+
+        mRootView = findViewById(R.id.root_view);
+        mUsernameLabel = findViewById(R.id.username_label);
+        mUsername = findViewById(R.id.username);
+        mPasswordLabel = findViewById(R.id.password_label);
+        mPassword = findViewById(R.id.password);
+    }
+}
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..72b92dc
--- /dev/null
+++ b/tests/contentcaptureservice/src/android/contentcaptureservice/cts/LoginActivityTest.java
@@ -0,0 +1,117 @@
+/*
+ * Copyright (C) 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR 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.assertLifecycleEvent;
+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.Helper.TAG;
+import static android.contentcaptureservice.cts.Helper.enableService;
+import static android.contentcaptureservice.cts.common.ActivitiesWatcher.ActivityLifecycle.DESTROYED;
+import static android.contentcaptureservice.cts.common.ActivitiesWatcher.ActivityLifecycle.RESUMED;
+import static android.view.contentcapture.ContentCaptureEvent.TYPE_ACTIVITY_PAUSED;
+import static android.view.contentcapture.ContentCaptureEvent.TYPE_ACTIVITY_RESUMED;
+import static android.view.contentcapture.ContentCaptureEvent.TYPE_ACTIVITY_STARTED;
+import static android.view.contentcapture.ContentCaptureEvent.TYPE_ACTIVITY_STOPPED;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import android.contentcaptureservice.cts.CtsSmartSuggestionsService.Session;
+import android.contentcaptureservice.cts.common.ActivitiesWatcher.ActivityWatcher;
+import android.support.test.rule.ActivityTestRule;
+import android.util.Log;
+import android.view.View;
+import android.view.autofill.AutofillId;
+import android.view.contentcapture.ContentCaptureEvent;
+
+import org.junit.Test;
+
+import java.util.List;
+
+public class LoginActivityTest extends AbstractContentCaptureIntegrationTest<LoginActivity> {
+
+    private static final ActivityTestRule<LoginActivity> sActivityRule = new ActivityTestRule<>(
+            LoginActivity.class, false, false);
+
+    public LoginActivityTest() {
+        super(LoginActivity.class);
+    }
+
+    @Override
+    protected ActivityTestRule<LoginActivity> getActivityTestRule() {
+        return sActivityRule;
+    }
+
+    // TODO(b/119638958): rename once we add moar tests
+    @Test
+    public void testIt() throws Exception {
+        enableService();
+
+        // TODO(b/119638958): move to super class
+        final ActivityWatcher watcher = mActivitiesWatcher.watch(LoginActivity.class);
+
+        final LoginActivity activity = launchActivity();
+        watcher.waitFor(RESUMED);
+
+        activity.finish();
+        watcher.waitFor(DESTROYED);
+
+        final CtsSmartSuggestionsService service = CtsSmartSuggestionsService.getInstance();
+        final Session session = service.getFinishedSession(LoginActivity.class);
+
+        assertRightActivity(session, activity);
+
+        final List<ContentCaptureEvent> events = session.getEvents();
+        Log.v(TAG, "events: " + events);
+        // TODO(b/119638958): ideally it should be 14 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.mRootView.getAutofillId();
+
+        assertThat(events).hasSize(18);
+        assertLifecycleEvent(events.get(0), TYPE_ACTIVITY_STARTED);
+        assertLifecycleEvent(events.get(1), TYPE_ACTIVITY_RESUMED);
+        assertViewAppeared(events.get(2), activity.mUsernameLabel, rootId);
+        assertViewAppeared(events.get(3), activity.mUsername, rootId);
+        assertViewAppeared(events.get(4), activity.mPasswordLabel, rootId);
+        assertViewAppeared(events.get(5), activity.mPassword, rootId);
+        // TODO(b/119638958): get rid of those intermediated parents
+        final View grandpa1 = (View) activity.mRootView.getParent();
+        final View grandpa2 = (View) grandpa1.getParent();
+        final View decorView = (View) grandpa2.getParent();
+
+        assertViewAppeared(events.get(6), activity.mRootView, grandpa1.getAutofillId());
+        assertViewAppeared(events.get(7), grandpa1, grandpa2.getAutofillId());
+        assertViewAppeared(events.get(8), grandpa2, decorView.getAutofillId());
+
+        // TODO(b/119638958): VIEW_DISAPPEARED events should be send before the activity
+        // stopped - if we don't deprecate the latter, we should change the manager to make sure
+        // they're send in that order (or dropped)
+        assertLifecycleEvent(events.get(9), TYPE_ACTIVITY_PAUSED);
+        assertLifecycleEvent(events.get(10), TYPE_ACTIVITY_STOPPED);
+
+        assertViewDisappeared(events.get(11), grandpa2.getAutofillId());
+        assertViewDisappeared(events.get(12), grandpa1.getAutofillId());
+        assertViewDisappeared(events.get(13), activity.mRootView.getAutofillId());
+        assertViewDisappeared(events.get(14), activity.mUsernameLabel.getAutofillId());
+        assertViewDisappeared(events.get(15), activity.mUsername.getAutofillId());
+        assertViewDisappeared(events.get(16), activity.mPasswordLabel.getAutofillId());
+        assertViewDisappeared(events.get(17), activity.mPassword.getAutofillId());
+    }
+}
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..79d625a
--- /dev/null
+++ b/tests/contentcaptureservice/src/android/contentcaptureservice/cts/common/ActivitiesWatcher.java
@@ -0,0 +1,181 @@
+/*
+ * Copyright (C) 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR 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:
+ *
+ * <ul>
+ *   <li>Just RESUMED and DESTROYED events.
+ *   <li>Just one occurrence of each event.
+ * </ul>
+ *
+ * <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);
+    }
+
+    @Override
+    public void onActivityStarted(Activity activity) {
+        Log.v(TAG, "onActivityStarted(): " + activity);
+    }
+
+    @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);
+    }
+
+    @Override
+    public void onActivityStopped(Activity activity) {
+        Log.v(TAG, "onActivityStopped(): " + activity);
+    }
+
+    @Override
+    public void onActivitySaveInstanceState(Activity activity, Bundle outState) {
+        Log.v(TAG, "onActivitySaveInstanceState(): " + activity);
+    }
+
+    @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());
+    }
+
+    /**
+     * 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 mResumedLatch = 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 RESUMED:
+                    return mResumedLatch;
+                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 {
+        RESUMED,
+        DESTROYED
+    }
+}
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/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..fe4ebd7
--- /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
+brycelee@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..52c93c1 100644
--- a/tests/framework/base/activitymanager/Android.mk
+++ b/tests/framework/base/activitymanager/Android.mk
@@ -34,10 +34,17 @@
     $(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 \
+    CtsMockInputMethod \
+    metrics-helper-lib
+
+# Merge resources & AndroidManifest.xml from MockIme cts package
+LOCAL_RESOURCE_DIR := $(LOCAL_PATH)/../../../inputmethod/res
+LOCAL_FULL_LIBS_MANIFEST_FILES := $(LOCAL_PATH)/../../../inputmethod/mockime/AndroidManifest_MockIme.xml
 
 LOCAL_CTS_TEST_PACKAGE := android.server
 
diff --git a/tests/framework/base/activitymanager/AndroidManifest.xml b/tests/framework/base/activitymanager/AndroidManifest.xml
old mode 100755
new mode 100644
index f442cdb6..d113cb4
--- a/tests/framework/base/activitymanager/AndroidManifest.xml
+++ b/tests/framework/base/activitymanager/AndroidManifest.xml
@@ -17,13 +17,9 @@
 
 <manifest xmlns:android="http://schemas.android.com/apk/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" />
 
     <application android:label="CtsActivityManagerDeviceTestCases">
         <uses-library android:name="android.test.runner" />
@@ -54,6 +50,23 @@
             android:label="MaxAspectRatioUnsetActivity"
             android:resizeableActivity="false" />
 
+        <activity
+            android:name="android.server.am.AspectRatioTests$MinAspectRatioActivity"
+            android:label="MinAspectRatioActivity"
+            android:minAspectRatio="5.0"
+            android:resizeableActivity="false" />
+
+        <activity
+            android:name="android.server.am.AspectRatioTests$MinAspectRatioResizeableActivity"
+            android:label="MinAspectRatioResizeableActivity"
+            android:minAspectRatio="5.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"/>
@@ -84,6 +97,35 @@
 
         <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.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" />
+
     </application>
 
     <instrumentation
diff --git a/tests/framework/base/activitymanager/AndroidTest.xml b/tests/framework/base/activitymanager/AndroidTest.xml
index 8a6c34f..76e1392 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" />
diff --git a/tests/framework/base/activitymanager/app/AndroidManifest.xml b/tests/framework/base/activitymanager/app/AndroidManifest.xml
index c977b37..6e8f623 100755
--- a/tests/framework/base/activitymanager/app/AndroidManifest.xml
+++ b/tests/framework/base/activitymanager/app/AndroidManifest.xml
@@ -259,7 +259,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"
@@ -378,6 +378,9 @@
                   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 +411,41 @@
         <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"/>
+
+        <!-- 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/Components.java b/tests/framework/base/activitymanager/app/src/android/server/am/Components.java
index a02fe85..63c9bd3 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,6 +77,8 @@
     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");
@@ -93,11 +96,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,6 +136,8 @@
             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 ASSISTANT_VOICE_INTERACTION_SERVICE =
             component("AssistantVoiceInteractionService");
@@ -277,8 +285,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 +334,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/LaunchBroadcastReceiver.java b/tests/framework/base/activitymanager/app/src/android/server/am/LaunchBroadcastReceiver.java
index f3cb966..774bd3d 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
@@ -19,6 +19,7 @@
 import android.content.BroadcastReceiver;
 import android.content.Context;
 import android.content.Intent;
+import android.os.Bundle;
 import android.util.Log;
 
 /** Broadcast receiver that can launch activities. */
@@ -28,7 +29,9 @@
     @Override
     public void onReceive(Context context, Intent intent) {
         try {
-            ActivityLauncher.launchActivityFromExtras(context, intent.getExtras());
+            final Bundle extras = intent.getExtras();
+            ActivityLauncher.launchActivityFromExtras(context, extras,
+                    CommandSession.handleForward(extras));
         } catch (SecurityException e) {
             Log.e(TAG, "SecurityException launching activity");
         }
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/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/PipActivity.java b/tests/framework/base/activitymanager/app/src/android/server/am/PipActivity.java
index 144e053..37d4ee1 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;
@@ -245,15 +244,6 @@
         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
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/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/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/app_base/src/android/server/am/AbstractLifecycleLogActivity.java b/tests/framework/base/activitymanager/app_base/src/android/server/am/AbstractLifecycleLogActivity.java
index 63e801e..3aa33a3 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,16 @@
 
 package android.server.am;
 
-import android.app.Activity;
 import android.content.res.Configuration;
 import android.graphics.Point;
 import android.os.Bundle;
+import android.server.am.CommandSession.BasicTestActivity;
 import android.util.DisplayMetrics;
 import android.util.Log;
 import android.view.Display;
 import android.view.WindowManager;
 
-public abstract class AbstractLifecycleLogActivity extends Activity {
+public abstract class AbstractLifecycleLogActivity extends BasicTestActivity {
 
     @Override
     protected void onCreate(Bundle icicle) {
@@ -35,7 +35,7 @@
 
     @Override
     protected void onStart() {
-        super.onResume();
+        super.onStart();
         Log.i(getTag(), "onStart");
     }
 
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/src/android/server/am/ActivityManagerActivityVisibilityTests.java b/tests/framework/base/activitymanager/src/android/server/am/ActivityManagerActivityVisibilityTests.java
index 1ea220a..1719efd 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,15 @@
 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.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 +71,7 @@
 
     @Presubmit
     @Test
+    @FlakyTest(bugId = 110276714)
     public void testTranslucentActivityOnTopOfPinnedStack() throws Exception {
         if (!supportsPip()) {
             return;
@@ -85,7 +85,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 +136,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 +175,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 +206,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 +246,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
 
@@ -370,7 +372,7 @@
             final LogSeparator logSeparator = separateLogs();
             launchActivity(TURN_SCREEN_ON_ATTR_ACTIVITY);
             mAmWmState.assertVisibility(TURN_SCREEN_ON_ATTR_ACTIVITY, true);
-            assertTrue("Display turns on", isDisplayOn());
+            assertTrue("Display turns on", isDisplayOn(DEFAULT_DISPLAY));
             assertSingleLaunch(TURN_SCREEN_ON_ATTR_ACTIVITY, logSeparator);
         }
     }
@@ -388,7 +390,7 @@
             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());
+            assertFalse("Display keeps off", isDisplayOn(DEFAULT_DISPLAY));
             assertSingleLaunchAndStop(TURN_SCREEN_ON_ATTR_ACTIVITY, logSeparator);
         }
     }
@@ -401,7 +403,7 @@
             final LogSeparator logSeparator = separateLogs();
             launchActivity(TURN_SCREEN_ON_SHOW_ON_LOCK_ACTIVITY);
             mAmWmState.assertVisibility(TURN_SCREEN_ON_SHOW_ON_LOCK_ACTIVITY, true);
-            assertTrue("Display turns on", isDisplayOn());
+            assertTrue("Display turns on", isDisplayOn(DEFAULT_DISPLAY));
             assertSingleLaunch(TURN_SCREEN_ON_SHOW_ON_LOCK_ACTIVITY, logSeparator);
         }
     }
@@ -413,7 +415,7 @@
             mAmWmState.waitForAllStoppedActivities();
             LogSeparator logSeparator = separateLogs();
             launchActivity(TURN_SCREEN_ON_ATTR_REMOVE_ATTR_ACTIVITY);
-            assertTrue("Display turns on", isDisplayOn());
+            assertTrue("Display turns on", isDisplayOn(DEFAULT_DISPLAY));
             assertSingleLaunch(TURN_SCREEN_ON_ATTR_REMOVE_ATTR_ACTIVITY, logSeparator);
 
             lockScreenSession.sleepDevice();
@@ -422,7 +424,7 @@
             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());
+            assertFalse("Display keeps off", isDisplayOn(DEFAULT_DISPLAY));
             assertSingleStartAndStop(TURN_SCREEN_ON_ATTR_REMOVE_ATTR_ACTIVITY, logSeparator);
         }
     }
@@ -435,14 +437,21 @@
             LogSeparator logSeparator = separateLogs();
             launchActivity(TURN_SCREEN_ON_SINGLE_TASK_ACTIVITY);
             mAmWmState.assertVisibility(TURN_SCREEN_ON_SINGLE_TASK_ACTIVITY, true);
-            assertTrue("Display turns on", isDisplayOn());
+            assertTrue("Display turns on", isDisplayOn(DEFAULT_DISPLAY));
             assertSingleLaunch(TURN_SCREEN_ON_SINGLE_TASK_ACTIVITY, logSeparator);
 
             lockScreenSession.sleepDevice();
+            // We should make sure test activity stopped to prevent a false alarm stop state
+            // included when separateLogs() called.
+            waitAndAssertActivityState(TURN_SCREEN_ON_SINGLE_TASK_ACTIVITY, STATE_STOPPED,
+                    "Activity should be stopped");
             logSeparator = separateLogs();
             launchActivity(TURN_SCREEN_ON_SINGLE_TASK_ACTIVITY);
             mAmWmState.assertVisibility(TURN_SCREEN_ON_SINGLE_TASK_ACTIVITY, true);
-            assertTrue("Display turns on", isDisplayOn());
+            // 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, logSeparator);
         }
     }
@@ -455,13 +464,9 @@
             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));
         }
     }
 }
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 41f4006..98c7cc7 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;
@@ -72,16 +69,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;
 
@@ -282,7 +269,8 @@
         // 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);
+        resizeStack(stack.mStackId, displayRect.left, displayRect.top, displayRect.width(),
+                displayRect.height());
 
         // Move activity back to fullscreen stack.
         setActivityTaskWindowingMode(activityName,
@@ -317,6 +305,7 @@
      */
     @Presubmit
     @Test
+    @FlakyTest(bugId = 110276714)
     public void testDialogWhenLargeSplitSmall() {
         assumeTrue("Skipping test: no multi-window support", supportsSplitScreenMultiWindow());
 
@@ -329,7 +318,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)
@@ -524,12 +513,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);
 
         // Finish activity
-        executeShellCommand(FINISH_ACTIVITY_BROADCAST);
+        mBroadcastActionTrigger.finishBroadcastReceiverActivity();
 
         // Verify that activity brought to front is in originally requested orientation.
         mAmWmState.computeState(LANDSCAPE_ORIENTATION_ACTIVITY);
@@ -552,10 +541,10 @@
         // Verify fixed-landscape
         LogSeparator logSeparator = separateLogs();
         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);
+        mBroadcastActionTrigger.finishBroadcastReceiverActivity();
 
         // Verify that activity brought to front is in originally requested orientation.
         mAmWmState.waitForActivityState(RESIZEABLE_ACTIVITY, STATE_RESUMED);
@@ -570,10 +559,10 @@
         // Verify fixed-portrait
         logSeparator = separateLogs();
         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);
+        mBroadcastActionTrigger.finishBroadcastReceiverActivity();
 
         // Verify that activity brought to front is in originally requested orientation.
         mAmWmState.waitForActivityState(RESIZEABLE_ACTIVITY, STATE_RESUMED);
@@ -695,12 +684,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);
 
         // 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);
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..f968182 100644
--- a/tests/framework/base/activitymanager/src/android/server/am/ActivityManagerConfigChangeTests.java
+++ b/tests/framework/base/activitymanager/src/android/server/am/ActivityManagerConfigChangeTests.java
@@ -42,8 +42,12 @@
 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.Arrays;
+import java.util.List;
 import java.util.regex.Matcher;
 import java.util.regex.Pattern;
 
@@ -240,7 +244,7 @@
 
         final LogSeparator logSeparator = separateLogs();
         // 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 {
@@ -321,4 +325,11 @@
         }
         fail("No fontPixelSize reported from activity " + activityName);
     }
+
+    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..5407f62 100644
--- a/tests/framework/base/activitymanager/src/android/server/am/ActivityManagerDisplayLockedKeyguardTests.java
+++ b/tests/framework/base/activitymanager/src/android/server/am/ActivityManagerDisplayLockedKeyguardTests.java
@@ -57,7 +57,8 @@
             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,14 +67,18 @@
 
             // 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 */);
         }
     }
@@ -86,11 +91,17 @@
         try (final VirtualDisplaySession virtualDisplaySession = new VirtualDisplaySession();
              final LockScreenSession lockScreenSession = new LockScreenSession()) {
             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();
@@ -103,14 +114,20 @@
         try (final VirtualDisplaySession virtualDisplaySession = new VirtualDisplaySession();
              final LockScreenSession lockScreenSession = new LockScreenSession()) {
             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..30de631 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,20 @@
 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 com.android.compatibility.common.util.SystemUtil;
+
 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;
 
@@ -212,6 +220,8 @@
         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;
@@ -245,6 +255,16 @@
             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;
@@ -353,7 +373,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 +447,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() {
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/ActivityManagerMultiDisplayTests.java b/tests/framework/base/activitymanager/src/android/server/am/ActivityManagerMultiDisplayTests.java
index 90658e5..4a78f48 100644
--- a/tests/framework/base/activitymanager/src/android/server/am/ActivityManagerMultiDisplayTests.java
+++ b/tests/framework/base/activitymanager/src/android/server/am/ActivityManagerMultiDisplayTests.java
@@ -16,61 +16,105 @@
 
 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.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.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.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.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.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.ActivitySession;
+import android.server.am.CommandSession.SizeInfo;
+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.concurrent.TimeUnit;
+import java.util.function.Predicate;
 import java.util.regex.Matcher;
 import java.util.regex.Pattern;
 
@@ -79,15 +123,7 @@
  *     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 +137,131 @@
      */
     @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.
+     */
+    @Test
+    public 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.
+     */
+    @Test
+    public 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.
+     */
+    @Test
+    public 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);
+            SystemUtil.runWithShellPermissionIdentity(
+                    () -> getLaunchActivityBuilder().setUseInstrumentation()
+                            .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);
             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 +271,26 @@
     @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);
-
-        // 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);
+        waitAndAssertTopResumedActivity(TEST_ACTIVITY, DEFAULT_DISPLAY,
+                "Activity launched on primary display must be focused");
     }
 
     /**
      * 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 +298,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 +346,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 +355,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 +378,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);
+                    }}
+            );
         }
     }
 
@@ -388,6 +441,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 +457,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);
@@ -421,6 +475,7 @@
      * primary display.
      */
     @Test
+    @FlakyTest(bugId = 77469851)
     public void testConsequentLaunchActivity() throws Exception {
         try (final VirtualDisplaySession virtualDisplaySession = new VirtualDisplaySession()) {
             // Create new virtual display.
@@ -428,26 +483,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 +507,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 +516,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 +541,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 +560,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 +568,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 +589,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 +601,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,11 +609,9 @@
 
             // 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();
 
@@ -603,6 +629,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,15 +644,14 @@
                     .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);
+                    }}
+            );
         }
     }
 
@@ -654,6 +680,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 +698,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 +731,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 +751,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 +777,7 @@
         final int focusedStackWindowingMode = mAmWmState.getAmState().getFrontStackWindowingMode(
                 DEFAULT_DISPLAY);
         // Finish probing activity.
-        executeShellCommand(FINISH_ACTIVITY_BROADCAST);
+        mBroadcastActionTrigger.finishBroadcastReceiverActivity();
 
         tryCreatingAndRemovingDisplayWithActivity(false /* splitScreen */,
                 focusedStackWindowingMode);
@@ -770,10 +803,10 @@
 
             // 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);
 
             // Destroy virtual display.
             logSeparator = separateLogs();
@@ -788,7 +821,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 +836,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 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 +930,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 +956,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 +1022,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 +1034,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 +1051,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 +1074,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 +1083,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 +1104,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,19 +1122,15 @@
             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();
 
@@ -1057,9 +1145,8 @@
             assertSecurityException("ActivityLauncher", logSeparator);
 
             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);
         }
     }
 
@@ -1087,6 +1174,7 @@
      * 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.
@@ -1114,13 +1202,12 @@
 
             // 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);
+            waitAndAssertTopResumedActivity(TEST_ACTIVITY, newDisplay.mId,
+                    "Launched activity must be on top");
+
             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");
 
             // Destroy the display and check if activities are removed from system.
             logSeparator = separateLogs();
@@ -1151,6 +1238,32 @@
     }
 
     /**
+     * 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.
+            SystemUtil.runWithShellPermissionIdentity(
+                    () -> getLaunchActivityBuilder().setUseInstrumentation()
+                            .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");
+    }
+
+    /**
      * Test that the update of display metrics updates all its content.
      */
     @Test
@@ -1163,9 +1276,8 @@
             // Launch a resizeable activity on new secondary display.
             final LogSeparator initialLogSeparator = separateLogs();
             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(
@@ -1215,6 +1327,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 +1343,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 +1354,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 +1379,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 +1396,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 +1417,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 +1468,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 +1478,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 +1492,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 +1505,53 @@
 
             // 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 when both displays have
+     * matching task.
+     */
+    @Test
+    public void testTaskMatchOrderAcrossDisplays() throws Exception {
+        getLaunchActivityBuilder().setUseInstrumentation()
+                .setTargetActivity(TEST_ACTIVITY).setNewTask(true)
+                .setDisplayId(DEFAULT_DISPLAY).execute();
+        final int defaultDisplayStackId = mAmWmState.getAmState().getFrontStackId(DEFAULT_DISPLAY);
+
+        try (final VirtualDisplaySession virtualDisplaySession = new VirtualDisplaySession()) {
+            final ActivityDisplay newDisplay = virtualDisplaySession.createDisplay();
+            SystemUtil.runWithShellPermissionIdentity(
+                    () -> getLaunchActivityBuilder().setUseInstrumentation()
+                            .setTargetActivity(TEST_ACTIVITY).setNewTask(true)
+                            .setDisplayId(newDisplay.mId).execute());
+            assertNotEquals("Top focus stack should not be on default display",
+                    defaultDisplayStackId, mAmWmState.getAmState().getFocusedStackId());
+
+            // Launch activity without specified display id to avoid adding
+            // FLAG_ACTIVITY_MULTIPLE_TASK flag to this launch. And without specify display id,
+            // AM should search a matching task on default display prior than other displays.
+            getLaunchActivityBuilder().setUseInstrumentation()
+                    .setTargetActivity(TEST_ACTIVITY).setNewTask(true).execute();
+            mAmWmState.assertFocusedStack("Top focus stack must be on the default display",
+                    defaultDisplayStackId);
+        }
+    }
+
+    /**
      * 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 +1560,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 +1576,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 +1648,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 +1668,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,8 +1680,10 @@
             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();
 
@@ -1584,7 +1706,9 @@
                 fail(RESIZEABLE_ACTIVITY + " has received " + lifecycleCounts.mStopCount
                         + " 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 +1718,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 +1731,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 +1742,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 +1761,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 +1783,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 +1795,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 +1805,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);
+            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();
             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 +1857,507 @@
 
             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();
+            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 {
+        final LogSeparator logSeparator;
+        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);
+
+            logSeparator = separateLogs();
+            // Destroy the display.
+        }
+
+        // Activity must be reparented to default display and relaunched.
+        assertActivityLifecycle(TEST_ACTIVITY, true /* relaunched */, logSeparator);
+        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 when target display does not support
+     * system decoration.
+     */
+    @Test
+    public void testImeShownInDefaultDisplayWhenNoSystemDecoration() throws Exception {
+        final long TIMEOUT_SOFT_INPUT = 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.
+            final ImeEventStream stream = mockImeSession.openEventStream();
+            imeTestActivitySession.runOnMainSyncAndWait(
+                    imeTestActivitySession.getActivity()::showSoftInput);
+            expectEvent(stream, event -> "showSoftInput".equals(event.getEventName()),
+                    TIMEOUT_SOFT_INPUT);
+
+            mAmWmState.waitAndAssertImeWindowShownOnDisplay(DEFAULT_DISPLAY);
+        }
+    }
+
+    @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
+    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
+    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
+    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);
+        }
+    }
+
+    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 +2432,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..0509e33 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;
@@ -41,7 +41,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,11 +49,8 @@
 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;
@@ -90,6 +86,11 @@
 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;
@@ -322,7 +323,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 +586,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 +606,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 +688,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 +707,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 +728,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 +797,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 +823,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)
@@ -851,7 +847,7 @@
         // 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);
+        mBroadcastActionTrigger.doAction(ACTION_ENTER_PIP);
         waitForEnterPip(PIP_ACTIVITY);
         assertPinnedStackExists();
         waitForValidPictureInPictureCallbacks(PIP_ACTIVITY, logSeparator);
@@ -935,7 +931,9 @@
 
         // 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
@@ -970,9 +968,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 +985,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 +1024,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,10 +1046,11 @@
         launchPinnedActivityAsTaskOverlay(TRANSLUCENT_TEST_ACTIVITY, taskId);
 
         // Finish the task overlay activity while animating and ensure that the PiP activity never
-        // got resumed
+        // got resumed.
         LogSeparator logSeparator = separateLogs();
-        executeShellCommand("am stack resize-animated " + stackId + " 20 20 500 500");
-        executeShellCommand("am broadcast -a " + TEST_ACTIVITY_ACTION_FINISH_SELF);
+        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...");
@@ -1249,9 +1243,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 +1256,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 +1293,7 @@
         offsetPipWithinMovementBounds(100 /* offsetY */, initialBounds, offsetBounds);
 
         // Finish the activity
-        executeShellCommand("am broadcast -a " + ACTION_FINISH);
+        mBroadcastActionTrigger.doAction(ACTION_FINISH);
         waitForPinnedStackRemoved();
         assertPinnedStackDoesNotExist();
 
@@ -1364,8 +1358,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();
@@ -1564,7 +1558,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 +1581,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 +1615,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..22702d5 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;
@@ -277,7 +276,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 +487,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 +504,9 @@
     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.assertNotExist(TEST_ACTIVITY);
         assertDockNotMinimized();
     }
 
@@ -545,8 +545,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 +563,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);
     }
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..0e58956
--- /dev/null
+++ b/tests/framework/base/activitymanager/src/android/server/am/ActivityMetricsLoggerTests.java
@@ -0,0 +1,344 @@
+/*
+ * Copyright (C) 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR 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.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_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.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
+    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
+    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);
+    }
+
+    /**
+     * 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.
+     */
+    @Test
+    public void testAppLaunchSetsWaitResultDelayData() {
+        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)));
+    }
+
+    /**
+     * 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
+    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
+    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
+    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/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..a313c35 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 = 5.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..797bb13 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
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..777823b 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;
@@ -29,11 +27,13 @@
 import static android.server.am.Components.SHOW_WHEN_LOCKED_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 org.junit.Assert.assertFalse;
 import static org.junit.Assert.assertTrue;
 import static org.junit.Assume.assumeTrue;
 
+import android.app.KeyguardManager;
 import android.content.ComponentName;
 
 import org.junit.Before;
@@ -44,12 +44,6 @@
  *     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 +71,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 +114,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 +131,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.
@@ -175,7 +187,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 +201,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 +244,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);
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..7a97d83 100755
--- a/tests/framework/base/activitymanager/src/android/server/am/KeyguardTests.java
+++ b/tests/framework/base/activitymanager/src/android/server/am/KeyguardTests.java
@@ -18,12 +18,9 @@
 
 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.KEYGUARD_LOCK_ACTIVITY;
@@ -38,6 +35,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 +44,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 +57,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 +82,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 +105,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 +126,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 +144,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 +163,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 +201,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 +211,54 @@
     }
 
     /**
+     * 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
@@ -259,7 +300,7 @@
             assertTrue(mAmWmState.getAmState().getKeyguardControllerState().keyguardShowing);
             launchActivity(BROADCAST_RECEIVER_ACTIVITY);
             launchActivity(TEST_ACTIVITY);
-            executeShellCommand(DISMISS_KEYGUARD_METHOD_BROADCAST);
+            mBroadcastActionTrigger.dismissKeyguardByMethod();
             assertOnDismissErrorInLogcat(logSeparator);
         }
     }
@@ -277,7 +318,7 @@
             mAmWmState.assertVisibility(TURN_SCREEN_ON_DISMISS_KEYGUARD_ACTIVITY, true);
             assertFalse(mAmWmState.getAmState().getKeyguardControllerState().keyguardShowing);
             assertOnDismissSucceededInLogcat(logSeparator);
-            assertTrue(isDisplayOn());
+            assertTrue(isDisplayOn(DEFAULT_DISPLAY));
         }
     }
 
@@ -290,7 +331,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 +345,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 +368,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);
         }
     }
 
@@ -355,7 +401,7 @@
             mAmWmState.assertVisibility(TURN_SCREEN_ON_ATTR_DISMISS_KEYGUARD_ACTIVITY, true);
             assertFalse(mAmWmState.getAmState().getKeyguardControllerState().keyguardShowing);
             assertOnDismissSucceededInLogcat(logSeparator);
-            assertTrue(isDisplayOn());
+            assertTrue(isDisplayOn(DEFAULT_DISPLAY));
         }
     }
 
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..ed3aeee 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());
         }
     }
 
@@ -118,7 +119,7 @@
             launchActivity(SHOW_WHEN_LOCKED_ATTR_ACTIVITY);
             mAmWmState.computeState(SHOW_WHEN_LOCKED_ATTR_ACTIVITY);
             assertEquals("Picked wrong transition", TRANSIT_KEYGUARD_OCCLUDE,
-                    mAmWmState.getWmState().getLastTransition());
+                    mAmWmState.getWmState().getDefaultDisplayLastTransition());
             assertSingleLaunch(SHOW_WHEN_LOCKED_ATTR_ACTIVITY, logSeparator);
         }
     }
@@ -131,13 +132,19 @@
             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());
+                    mAmWmState.getWmState().getDefaultDisplayLastTransition());
             assertSingleLaunch(SHOW_WHEN_LOCKED_ATTR_REMOVE_ATTR_ACTIVITY, logSeparator);
 
+            // 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);
+            // 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, logSeparator);
         }
     }
@@ -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 3efe3bb..a352aa1 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/lifecycle/ActivityLifecycleTests.java b/tests/framework/base/activitymanager/src/android/server/am/lifecycle/ActivityLifecycleTests.java
index 153a4cc..4d11d47 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
@@ -322,8 +322,7 @@
         waitAndAssertActivityStates(state(callbackTrackingActivity, ON_RESUME));
 
         // Enter split screen
-        moveTaskToPrimarySplitScreen(callbackTrackingActivity.getTaskId(),
-                true /* launchSideActivityIfNeeded */);
+        moveTaskToPrimarySplitScreen(callbackTrackingActivity.getTaskId(), true /* showRecents */);
 
         // Launch second activity to pause first
         // Create an ActivityMonitor that catch ChildActivity and return mock ActivityResult:
@@ -342,6 +341,9 @@
         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)
         getLaunchActivityBuilder().execute();
 
@@ -382,8 +384,7 @@
         waitAndAssertActivityStates(state(firstActivity, ON_RESUME));
 
         // Enter split screen
-        moveTaskToPrimarySplitScreen(firstActivity.getTaskId(),
-                true /* launchSideActivityIfNeeded */);
+        moveTaskToPrimarySplitScreen(firstActivity.getTaskId(), true /* showRecents */);
 
         // Launch second activity to pause first
         final Activity secondActivity =
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..ef31add 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
@@ -17,18 +17,47 @@
 package android.server.am.lifecycle;
 
 import android.app.Activity;
+import android.content.ComponentName;
 import android.content.Intent;
-import android.server.am.lifecycle.ActivityLifecycleClientTestBase;
+import android.os.Bundle;
+import android.server.am.ActivityLauncher;
+import android.support.test.InstrumentationRegistry;
 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.lifecycle.LifecycleLog.ActivityCallback.ON_RESUME;
 
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotEquals;
+
 /**
  * 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 +81,366 @@
         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));
+
+    }
+
+    /**
+     * 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/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..9433c76 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...");
     }
 
@@ -307,10 +321,26 @@
                 "***Waiting for focused stack...");
     }
 
-    void waitForAppTransitionIdle() {
+    void waitForPendingActivityContain(ComponentName activity) {
+        waitForWithAmState(state -> state.pendingActivityContain(activity),
+                "***Waiting for activity in pending list...");
+    }
+
+    void waitForAppTransitionIdleOnDisplay(int displayId) {
         waitForWithWmState(
-                state -> WindowManagerState.APP_STATE_IDLE.equals(state.getAppTransitionState()),
-                "***Waiting for app transition idle...");
+                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);
     }
 
     void waitForWithAmState(Predicate<ActivityManagerState> waitCondition, String message) {
@@ -371,7 +401,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 +523,7 @@
         return false;
     }
 
-    ActivityManagerState getAmState() {
+    public ActivityManagerState getAmState() {
         return mAmState;
     }
 
@@ -503,9 +533,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,12 +562,7 @@
         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));
-    }
-
-    void assertFrontStack(String msg, int windowingMode, int activityType) {
+    public void assertFrontStack(String msg, int windowingMode, int activityType) {
         if (windowingMode != WINDOWING_MODE_UNDEFINED) {
             assertEquals(msg, windowingMode,
                     mAmState.getFrontStackWindowingMode(DEFAULT_DISPLAY));
@@ -551,7 +577,6 @@
         assertEquals(msg, activityType, mWmState.getFrontStackActivityType(DEFAULT_DISPLAY));
     }
 
-    @Deprecated
     void assertFocusedStack(String msg, int stackId) {
         assertEquals(msg, stackId, mAmState.getFocusedStackId());
     }
@@ -565,23 +590,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 +636,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 +681,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 +696,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 +927,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..63ec592 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
@@ -29,6 +29,7 @@
 import android.content.Intent;
 import android.net.Uri;
 import android.os.Bundle;
+import android.server.am.CommandSession.LaunchInjector;
 import android.text.TextUtils;
 import android.util.Log;
 
@@ -90,10 +91,29 @@
      * it's always written to logs.
      */
     public static final String KEY_SUPPRESS_EXCEPTIONS = "suppress_exceptions";
+    /**
+     * 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 +149,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,8 +177,12 @@
                 // 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);
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..4496352 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;
@@ -32,6 +32,8 @@
 import android.os.ParcelFileDescriptor;
 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;
@@ -67,10 +69,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 +113,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 +161,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 +215,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 +307,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) {
@@ -348,6 +372,10 @@
         return mStacks.size();
     }
 
+    int getDisplayCount() {
+        return mDisplays.size();
+    }
+
     boolean containsActivity(ComponentName activityName) {
         final String fullName = getActivityName(activityName);
         for (ActivityStack stack : mStacks) {
@@ -510,12 +538,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,21 +567,63 @@
         return null;
     }
 
+    /**
+     * 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;
+    }
+
+    boolean pendingActivityContain(ComponentName activityName) {
+        return mPendingActivities.contains(getActivityName(activityName));
+    }
+
     static class ActivityDisplay extends ActivityContainer {
 
         int mId;
         ArrayList<ActivityStack> mStacks = new ArrayList<>();
+        int mFocusedStackId;
+        String mResumedActivity;
 
         ActivityDisplay(ActivityDisplayProto proto, ActivityManagerState amState) {
             super(proto.configurationContainer);
             mId = proto.id;
+            mFocusedStackId = proto.focusedStackId;
+            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);
                 }
             }
         }
@@ -618,7 +688,7 @@
         }
     }
 
-    static class ActivityTask extends ActivityContainer {
+    public static class ActivityTask extends ActivityContainer {
 
         int mTaskId;
         int mStackId;
@@ -651,16 +721,8 @@
             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;
         }
     }
 
@@ -716,13 +778,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..5d09c94 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,17 @@
 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_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 +81,11 @@
 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 org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertNotNull;
 import static org.junit.Assert.assertTrue;
 import static org.junit.Assert.fail;
@@ -76,17 +95,35 @@
 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.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.graphics.Bitmap;
+import android.graphics.Rect;
+import android.hardware.display.DisplayManager;
 import android.os.Bundle;
 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.LaunchInjector;
+import android.server.am.CommandSession.LaunchProxy;
 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 +135,51 @@
 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.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 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";
+    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;
+    static {
+        final List<String> testPackages = new ArrayList<>(3);
+        testPackages.add(TEST_PACKAGE);
+        testPackages.add(SECOND_TEST_PACKAGE);
+        testPackages.add(THIRD_TEST_PACKAGE);
+        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 +189,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 =
@@ -226,26 +262,131 @@
         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();
+
+    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)));
+        }
     }
 
-    protected ActivityAndWindowManagersState mAmWmState = new ActivityAndWindowManagersState();
+    /**
+     * 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;
+
+        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();
+        }
+
+        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 +395,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 +489,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();
     }
@@ -373,40 +542,47 @@
 
     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 showRecents   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 showRecents) {
+        SystemUtil.runWithShellPermissionIdentity(() -> {
+            mAtm.setTaskWindowingModeSplitScreenPrimary(taskId,
+                    SPLIT_SCREEN_CREATE_MODE_TOP_OR_LEFT, true /* onTop */,
+                    false /* animate */,
+                    null /* initialBounds */, showRecents);
+            mAmWmState.waitForRecentsActivityVisible();
 
-        if (mAmWmState.getAmState().isHomeRecentsComponent() && launchSideActivityIfNeeded) {
-            // Launch Placeholder Recents
-            final Activity recentsActivity = mSideActivityRule.launchActivity(new Intent());
-            mAmWmState.waitForActivityState(recentsActivity.getComponentName(), STATE_RESUMED);
-        }
+            if (mAmWmState.getAmState().isHomeRecentsComponent() && showRecents) {
+                // Launch Placeholder Recents
+                final Activity recentsActivity = mSideActivityRule.launchActivity(
+                        new Intent());
+                mAmWmState.waitForActivityState(recentsActivity.getComponentName(), STATE_RESUMED);
+            }
+        });
     }
 
     /**
@@ -438,7 +614,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 +625,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 +637,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 +706,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));
+    }
+
+    protected void waitAndAssertTopResumedActivity(ComponentName activityName, int displayId,
+            String message) throws Exception {
+        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 +764,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 +791,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,6 +838,53 @@
         }
     }
 
+    /**
+     * 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;
 
@@ -657,9 +905,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 +933,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 +950,22 @@
             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() {
             setLockDisabled(mIsLockDisabled);
             if (mLockCredentialSet) {
                 removeLockCredential();
@@ -717,7 +973,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();
@@ -841,6 +1105,7 @@
     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 +1137,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.
      */
@@ -1230,7 +1518,7 @@
         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");
@@ -1393,15 +1681,51 @@
         }
     }
 
-    protected void stopTestPackage(final ComponentName activityName) {
-        executeShellCommand("am force-stop " + activityName.getPackageName());
+    /** Assert the activity is either relaunched or received configuration changed. */
+    List<ActivityCallback> assertActivityLifecycle(ActivitySession activitySession,
+            boolean relaunched) {
+        final String name = activitySession.getName();
+        final List<ActivityCallback> callbackHistory = activitySession.takeCallbackHistory();
+        final int[] lifecycleCounts = getActivityLifecycleCounts(callbackHistory);
+        if (relaunched) {
+            if (lifecycleCounts[ActivityCallback.ON_DESTROY.ordinal()] < 1) {
+                fail(name + " must have been destroyed. callbacks=" + callbackHistory);
+            }
+            if (lifecycleCounts[ActivityCallback.ON_CREATE.ordinal()] < 1) {
+                fail(name + " must have been (re)created. callbacks=" + callbackHistory);
+            }
+            return callbackHistory;
+        }
+        if (lifecycleCounts[ActivityCallback.ON_DESTROY.ordinal()] > 0) {
+            fail(name + " must *NOT* have been destroyed. callbacks=" + callbackHistory);
+        }
+        if (lifecycleCounts[ActivityCallback.ON_CREATE.ordinal()] > 0) {
+            fail(name + " must *NOT* have been (re)created. callbacks=" + callbackHistory);
+        }
+        if (lifecycleCounts[ActivityCallback.ON_CONFIGURATION_CHANGED.ordinal()] < 1) {
+            fail(name + " must have received configuration changed. callbacks=" + callbackHistory);
+        }
+        return callbackHistory;
+    }
+
+    /** @return A array contains the lifecycle count by the ordinal of {@link ActivityCallback}. */
+    int[] getActivityLifecycleCounts(List<ActivityCallback> lifecycleCallbacks) {
+        final int[] counts = new int[ActivityCallback.SIZE];
+        for (ActivityCallback callback : lifecycleCallbacks) {
+            counts[callback.ordinal()]++;
+        }
+        return counts;
+    }
+
+    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,16 +1735,21 @@
         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
@@ -1452,6 +1781,11 @@
             return this;
         }
 
+        public LaunchActivityBuilder allowMultipleInstances(boolean allowMultipleInstances) {
+            mAllowMultipleInstances = allowMultipleInstances;
+            return this;
+        }
+
         public LaunchActivityBuilder setReorderToFront(boolean reorderToFront) {
             mReorderToFront = reorderToFront;
             return this;
@@ -1480,6 +1814,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 +1853,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 +1902,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 +1944,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 +1971,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..00be3ab
--- /dev/null
+++ b/tests/framework/base/activitymanager/util/src/android/server/am/CommandSession.java
@@ -0,0 +1,1078 @@
+/*
+ * Copyright (C) 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR 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.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;
+
+/**
+ * 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;
+
+        @Override
+        protected void onCreate(Bundle savedInstanceState) {
+            super.onCreate(savedInstanceState);
+
+            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();
+            }
+        }
+
+        @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);
+        }
+
+        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);
+            }
+        }
+
+        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 == null || !view.isAttachedToWindow()) {
+                throw new IllegalStateException("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;
+
+        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;
+        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();
+
+            displayId = display.getDisplayId();
+            rotation = display.getRotation();
+            sizeInfo = new SizeInfo(display, metrics, config);
+        }
+
+        @Override
+        public String toString() {
+            return "ConfigInfo: {displayId=" + displayId + " rotation=" + rotation
+                    + " sizeInfo=" + 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) {
+            final Point displaySize = new Point();
+            display.getSize(displaySize);
+
+            widthDp = config.screenWidthDp;
+            heightDp = config.screenHeightDp;
+            displayWidth = displaySize.x;
+            displayHeight = displaySize.y;
+            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/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..0eceaf4 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) {
@@ -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/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..5caa9d6 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;
@@ -74,6 +75,7 @@
 @RunWith(AndroidJUnit4.class)
 @Presubmit
 public class DisplayCutoutTests {
+    static final Rect ZERO_RECT = new Rect();
 
     @Rule
     public final ErrorCollector mErrorCollector = new ErrorCollector();
@@ -133,6 +135,7 @@
 
         if (displayCutout != null) {
             commonAsserts(activity, insets, displayCutout);
+            assertCutoutsAreConsistentWithInsets(insets, displayCutout);
         }
         test.run(activity, insets, displayCutout, ROOT);
 
@@ -148,11 +151,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 +222,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 +238,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 +351,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..b0955af
--- /dev/null
+++ b/tests/framework/base/windowmanager/src/android/server/wm/LayoutTests.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 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.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 InterruptedException {
+        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);
+
+        // Send a key event to the system.
+        mInstrumentation.sendKeyDownUpSync(KeyEvent.KEYCODE_0);
+        synchronized (this) {
+            wait(TIMEOUT_RECEIVE_KEY);
+        }
+
+        // The window must get the key event.
+        assertTrue("Child window must get key event.", 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) {
+                mChildWindowGotKeyEvent = true;
+                stopWaiting();
+                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..51bf0d2
--- /dev/null
+++ b/tests/framework/base/windowmanager/src/android/server/wm/WindowFocusTests.java
@@ -0,0 +1,470 @@
+/*
+ * Copyright (C) 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR 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 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
+@FlakyTest(detail = "Can be promoted to pre-submit once confirmed stable.")
+@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);
+        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 Display secondaryDisplay = displaySession.createDisplay(getTargetContext());
+            final SecondaryActivity secondaryActivity
+                    = startActivity(SecondaryActivity.class, secondaryDisplay.getDisplayId());
+            sendAndAssertTargetConsumedKey(secondaryActivity, KEYCODE_2, INVALID_DISPLAY);
+            sendAndAssertTargetConsumedKey(secondaryActivity, KEYCODE_3,
+                    secondaryDisplay.getDisplayId());
+
+            primaryActivity.assertWindowFocusState(true /* hasFocus */);
+            sendAndAssertTargetConsumedKey(primaryActivity, KEYCODE_4, DEFAULT_DISPLAY);
+
+            // 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, secondaryDisplay.getDisplayId());
+            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.
+            secondaryActivity.waitAssertAndConsumeKeyEvent(ACTION_UP, KEYCODE_5, FLAG_CANCELED);
+            secondaryActivity.waitAssertAndConsumeKeyEvent(ACTION_UP, KEYCODE_7, 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 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
+    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 Display secondaryDisplay = displaySession.createDisplay(getTargetContext());
+            final SecondaryActivity secondaryActivity
+                    = startActivity(SecondaryActivity.class, secondaryDisplay.getDisplayId());
+
+            // 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 Display secondaryDisplay = displaySession.createDisplay(getTargetContext());
+            secondaryActivity
+                    = startActivity(SecondaryActivity.class, secondaryDisplay.getDisplayId());
+        }
+        // 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/AndroidTest.xml b/tests/inputmethod/AndroidTest.xml
index f749065..a1811bc 100644
--- a/tests/inputmethod/AndroidTest.xml
+++ b/tests/inputmethod/AndroidTest.xml
@@ -18,6 +18,7 @@
 <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" />
     <target_preparer class="com.android.tradefed.targetprep.suite.SuiteApkInstaller">
         <option name="cleanup-apks" value="true" />
         <!--
diff --git a/tests/inputmethod/mockime/AndroidManifest_MockIme.xml b/tests/inputmethod/mockime/AndroidManifest_MockIme.xml
new file mode 100644
index 0000000..4a1f319
--- /dev/null
+++ b/tests/inputmethod/mockime/AndroidManifest_MockIme.xml
@@ -0,0 +1,41 @@
+<?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.view.inputmethod.cts"
+    android:targetSandboxVersion="2">
+
+    <application
+        android:multiArch="true"
+        android:supportsRtl="true">
+
+        <!-- In order to test typical use cases, let this MockIME run in a separate process -->
+        <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>
+
+    </application>
+</manifest>
diff --git a/tests/inputmethod/src/android/view/inputmethod/cts/FocusHandlingTest.java b/tests/inputmethod/src/android/view/inputmethod/cts/FocusHandlingTest.java
index 9b3034e..f8faa15 100644
--- a/tests/inputmethod/src/android/view/inputmethod/cts/FocusHandlingTest.java
+++ b/tests/inputmethod/src/android/view/inputmethod/cts/FocusHandlingTest.java
@@ -74,7 +74,7 @@
 
     public EditText launchTestActivity(String marker) {
         final AtomicReference<EditText> editTextRef = new AtomicReference<>();
-        TestActivity.startSync(activity-> {
+        TestActivity.startSyncAndWait(activity-> {
             final LinearLayout layout = new LinearLayout(activity);
             layout.setOrientation(LinearLayout.VERTICAL);
 
diff --git a/tests/inputmethod/src/android/view/inputmethod/cts/OnScreenPositionTest.java b/tests/inputmethod/src/android/view/inputmethod/cts/OnScreenPositionTest.java
index b320f88..29f99e9 100644
--- a/tests/inputmethod/src/android/view/inputmethod/cts/OnScreenPositionTest.java
+++ b/tests/inputmethod/src/android/view/inputmethod/cts/OnScreenPositionTest.java
@@ -58,7 +58,7 @@
 
     public EditText launchTestActivity() {
         final AtomicReference<EditText> editTextRef = new AtomicReference<>();
-        TestActivity.startSync(activity -> {
+        TestActivity.startSyncAndWait(activity -> {
             final LinearLayout layout = new LinearLayout(activity);
             layout.setOrientation(LinearLayout.VERTICAL);
 
diff --git a/tests/inputmethod/src/android/view/inputmethod/cts/SearchViewTest.java b/tests/inputmethod/src/android/view/inputmethod/cts/SearchViewTest.java
index 619c852..f578e03 100644
--- a/tests/inputmethod/src/android/view/inputmethod/cts/SearchViewTest.java
+++ b/tests/inputmethod/src/android/view/inputmethod/cts/SearchViewTest.java
@@ -50,7 +50,7 @@
 
     public SearchView launchTestActivity() {
         final AtomicReference<SearchView> searchViewRef = new AtomicReference<>();
-        TestActivity.startSync(activity -> {
+        TestActivity.startSyncAndWait(activity -> {
             final LinearLayout layout = new LinearLayout(activity);
             layout.setOrientation(LinearLayout.VERTICAL);
 
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..f44c3f8 100644
--- a/tests/inputmethod/src/android/view/inputmethod/cts/util/TestActivity.java
+++ b/tests/inputmethod/src/android/view/inputmethod/cts/util/TestActivity.java
@@ -34,7 +34,6 @@
 import java.util.function.Function;
 
 public final class TestActivity extends Activity {
-
     private static final AtomicReference<Function<TestActivity, View>> sInitializer =
             new AtomicReference<>();
 
@@ -44,6 +43,8 @@
 
     private long mOnBackPressedCallCount;
 
+    private boolean mEnterAnimationComplete = false;
+
     /**
      * Controls how {@link #onBackPressed()} behaves.
      *
@@ -94,6 +95,25 @@
     }
 
     /**
+     * Launches {@link TestActivity} with the given initialization logic for content view and waits
+     * for the enter animation to complete.
+     *
+     * <p>As long as you are using {@link android.support.test.runner.AndroidJUnitRunner}, the test
+     * runner automatically calls {@link Activity#finish()} for the {@link Activity} launched when
+     * the test finished.  You do not need to explicitly call {@link Activity#finish()}.</p>
+     *
+     * @param activityInitializer initializer to supply {@link View} to be passed to
+     *                           {@link Activity#setContentView(View)}
+     * @return {@link TestActivity} launched
+     */
+    public static TestActivity startSyncAndWait(
+            @NonNull Function<TestActivity, View> activityInitializer) {
+        TestActivity activity = startSync(activityInitializer, 0 /* noAnimation */);
+        activity.waitForEnterAnimationComplete();
+        return activity;
+    }
+
+    /**
      * Launches {@link TestActivity} with the given initialization logic for content view.
      *
      * <p>As long as you are using {@link android.support.test.runner.AndroidJUnitRunner}, the test
@@ -135,6 +155,30 @@
                 .getInstrumentation().startActivitySync(intent);
     }
 
+    @Override
+    public void onStop() {
+        super.onStop();
+        mEnterAnimationComplete = false;
+    }
+
+    @Override
+    public void onEnterAnimationComplete() {
+        synchronized (this) {
+           mEnterAnimationComplete = true;
+           notifyAll();
+        }
+    }
+
+    private void waitForEnterAnimationComplete() {
+        synchronized(this) {
+            if (mEnterAnimationComplete == false) {
+                try {
+                    wait(5000);
+                } catch (InterruptedException e) {}
+            }
+        }
+    }
+
     /**
      * Updates {@link WindowManager.LayoutParams#softInputMode}.
      *
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 8e455bf..badcaec 100644
--- a/tests/libcore/wycheproof-bc/AndroidTest.xml
+++ b/tests/libcore/wycheproof-bc/AndroidTest.xml
@@ -32,5 +32,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 2a1f2b5..0119e6b 100644
--- a/tests/libcore/wycheproof/AndroidTest.xml
+++ b/tests/libcore/wycheproof/AndroidTest.xml
@@ -31,5 +31,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/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..7e31a44
--- /dev/null
+++ b/tests/providerui/src/android/providerui/cts/MediaStoreUiTest.java
@@ -0,0 +1,367 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR 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.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) {
+        mMediaStoreUri = uri;
+        latch.countDown();
+    }
+
+    private Uri acquireAccess(File file, String directoryName) {
+        StorageManager storageManager =
+                (StorageManager) mActivity.getSystemService(Context.STORAGE_SERVICE);
+
+        // Request access from DocumentsUI
+        final Intent intent = new Intent(Intent.ACTION_OPEN_DOCUMENT_TREE);
+        final StorageVolume volume = storageManager.getStorageVolume(file);
+        // TODO(b/118898214): clean up this flow to not rely on private APIs
+        intent.putExtra(DocumentsContract.EXTRA_INITIAL_URI, DocumentsContract.buildDocumentUri(
+                "com.android.externalstorage.documents", "primary:" + directoryName));
+        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();
+
+        // 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 44135bc..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,57 +89,95 @@
     }
 
     public void testAccelerometerRange() {
-        checkSensorRangeAndFrequency(
-                mSensorManager.getDefaultSensor(Sensor.TYPE_ACCELEROMETER),
-                ACCELEROMETER_MAX_RANGE,
-                ACCELEROMETER_MIN_FREQUENCY,
-                ACCELEROMETER_MAX_FREQUENCY);
-  }
+        double hifiMaxFrequency = ApiLevelUtil.isAtLeast(Build.VERSION_CODES.N) ?
+                ACCELEROMETER_HIFI_MAX_FREQUENCY :
+                ACCELEROMETER_HIFI_MAX_FREQUENCY_BEFORE_N;
 
-  public void testGyroscopeRange() {
         checkSensorRangeAndFrequency(
-                mSensorManager.getDefaultSensor(Sensor.TYPE_GYROSCOPE),
+                Sensor.TYPE_ACCELEROMETER,
+                ACCELEROMETER_MAX_RANGE,
+                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(
-                mSensorManager.getDefaultSensor(Sensor.TYPE_MAGNETIC_FIELD),
+                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(
-                    mSensorManager.getDefaultSensor(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(
-          Sensor sensor, 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) {
+            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/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/AndroidManifest.xml b/tests/tests/app.usage/AndroidManifest.xml
index 9c4342f..ecbc797 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.
@@ -38,7 +39,10 @@
         <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" />
+        <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 f484075..c3335db 100644
--- a/tests/tests/app.usage/AndroidTest.xml
+++ b/tests/tests/app.usage/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="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="CtsUsageStatsTestCases.apk" />
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/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/UsageStatsTest.java b/tests/tests/app.usage/src/android/app/usage/cts/UsageStatsTest.java
index a045488..4e9945e 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
@@ -164,7 +164,9 @@
         while (events.hasNextEvent()) {
             UsageEvents.Event event = new UsageEvents.Event();
             assertTrue(events.getNextEvent(event));
-            eventList.add(event);
+            if (mTargetPackage.equals(event.getPackageName())) {
+                eventList.add(event);
+            }
         }
 
         // Find the last Activity's MOVE_TO_FOREGROUND event.
@@ -190,7 +192,6 @@
 
             // 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());
 
@@ -198,7 +199,6 @@
             // last activity.
             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());
             }
@@ -848,10 +848,56 @@
         try {
             mUsageStatsManager.registerAppUsageObserver(0, new String[] {"com.android.settings"},
                     1, java.util.concurrent.TimeUnit.HOURS, null);
+            fail("Should throw SecurityException");
         } catch (SecurityException e) {
-            return;
+            // Exception expected
         }
-        fail("Should throw SecurityException");
+
+        try {
+            mUsageStatsManager.registerUsageSessionObserver(0, new String[]{"com.android.settings"},
+                    1, java.util.concurrent.TimeUnit.HOURS, 10,
+                    java.util.concurrent.TimeUnit.SECONDS, null, null);
+            fail("Should throw SecurityException");
+        } catch (SecurityException e) {
+            // Exception expected
+        }
+    }
+
+    @Test
+    public void testForegroundService() throws Exception {
+        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);
     }
 
     private void pressWakeUp() {
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/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/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/Android.mk b/tests/tests/batterysaving/Android.mk
index 8ba95d6..1cc06e2 100755
--- a/tests/tests/batterysaving/Android.mk
+++ b/tests/tests/batterysaving/Android.mk
@@ -27,6 +27,7 @@
     mockito-target-minus-junit4 \
     compatibility-device-util \
     ctstestrunner \
+    collector-device-lib \
     ub-uiautomator
 
 LOCAL_JAVA_LIBRARIES := android.test.runner.stubs android.test.base.stubs
diff --git a/tests/tests/batterysaving/AndroidManifest.xml b/tests/tests/batterysaving/AndroidManifest.xml
index 392a7aa..343a273 100755
--- a/tests/tests/batterysaving/AndroidManifest.xml
+++ b/tests/tests/batterysaving/AndroidManifest.xml
@@ -16,6 +16,8 @@
 <manifest xmlns:android="http://schemas.android.com/apk/res/android"
     package="android.os.cts.batterysaving">
 
+    <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
+
     <application>
         <uses-library android:name="android.test.runner" />
     </application>
diff --git a/tests/tests/batterysaving/AndroidTest.xml b/tests/tests/batterysaving/AndroidTest.xml
index d14c61f..fff0c0d 100644
--- a/tests/tests/batterysaving/AndroidTest.xml
+++ b/tests/tests/batterysaving/AndroidTest.xml
@@ -33,10 +33,23 @@
     <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" >
         <option name="package" value="android.os.cts.batterysaving" />
         <option name="runtime-hint" value="10m00s" />
+
+        <!-- This is needed so that FilePullerLogCollector can read files in /sdcard/cts_text_dump/ -->
+        <option name="isolated-storage" value="false" />
     </test>
+
+    <!--
+      Pull out the dump files created by SystemUtil.runCommandAndDump().
+      Note in order for it to work, isolated-storage must be set to false in the test tag above.
+    -->
+    <metrics_collector class="com.android.tradefed.device.metric.FilePullerLogCollector">
+        <option name="directory-keys" value="/sdcard/cts_text_dump/" />
+    </metrics_collector>
 </configuration>
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..236b3b2 100644
--- a/tests/tests/batterysaving/src/android/os/cts/batterysaving/BatterySavingTestBase.java
+++ b/tests/tests/batterysaving/src/android/os/cts/batterysaving/BatterySavingTestBase.java
@@ -17,18 +17,20 @@
 
 import static com.android.compatibility.common.util.BatteryUtils.runDumpsysBatteryReset;
 import static com.android.compatibility.common.util.BatteryUtils.turnOnScreen;
-import static com.android.compatibility.common.util.SystemUtil.runCommandAndPrintOnLogcat;
+import static com.android.compatibility.common.util.SystemUtil.runCommandAndDump;
 import static com.android.compatibility.common.util.SystemUtil.runShellCommand;
 import static com.android.compatibility.common.util.TestUtils.waitUntil;
 
 import android.content.Context;
 import android.content.pm.PackageManager;
+import android.device.loggers.TestLogData;
 import android.location.LocationManager;
 import android.os.BatteryManager;
 import android.os.PowerManager;
 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;
 
@@ -47,22 +49,23 @@
 
     protected final BroadcastRpc mRpc = new BroadcastRpc();
 
+    @Rule
+    public TestLogData mLogs = new TestLogData();
+
     private final OnFailureRule mDumpOnFailureRule = new OnFailureRule(TAG) {
         @Override
         protected void onTestFailure(Statement base, Description description, Throwable t) {
-            runCommandAndPrintOnLogcat(TAG, "dumpsys power");
-            runCommandAndPrintOnLogcat(TAG, "dumpsys alarm");
-            runCommandAndPrintOnLogcat(TAG, "dumpsys jobscheduler");
-            runCommandAndPrintOnLogcat(TAG, "dumpsys content");
+            runCommandAndDump(TAG, "dumpsys power", mLogs, "test failure");
+            runCommandAndDump(TAG, "dumpsys alarm", mLogs, "test failure");
+            runCommandAndDump(TAG, "dumpsys jobscheduler", mLogs, "test failure");
+            runCommandAndDump(TAG, "dumpsys content", mLogs, "test failure");
         }
     };
 
     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/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 76e2e3c..41e8c71 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" />
     </application>
diff --git a/tests/tests/car/src/android/car/cts/CarPackageManagerTest.java b/tests/tests/car/src/android/car/cts/CarPackageManagerTest.java
index 0c3aebc..3f3c9a9 100644
--- a/tests/tests/car/src/android/car/cts/CarPackageManagerTest.java
+++ b/tests/tests/car/src/android/car/cts/CarPackageManagerTest.java
@@ -116,9 +116,9 @@
     @Ignore // Enable when b/120125891 is fixed
     public void testServiceDistractionOptimized() throws Exception {
         assertFalse(mCarPm.isServiceDistractionOptimized("com.basic.package", ""));
-        assertTrue(mCarPm.isServiceDistractionOptimized("com.android.settings", "Any"));
-        assertTrue(mCarPm.isServiceDistractionOptimized("com.android.settings", ""));
-        assertTrue(mCarPm.isServiceDistractionOptimized("com.android.settings", null));
+        assertTrue(mCarPm.isServiceDistractionOptimized("com.android.car.settings", "Any"));
+        assertTrue(mCarPm.isServiceDistractionOptimized("com.android.car.settings", ""));
+        assertTrue(mCarPm.isServiceDistractionOptimized("com.android.car.settings", null));
 
         try {
             mCarPm.isServiceDistractionOptimized(null, "Any");
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 507d2a2..204d339 100644
--- a/tests/tests/carrierapi/AndroidTest.xml
+++ b/tests/tests/carrierapi/AndroidTest.xml
@@ -26,6 +26,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..a4e5b1f 100644
--- a/tests/tests/content/AndroidManifest.xml
+++ b/tests/tests/content/AndroidManifest.xml
@@ -144,9 +144,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 +167,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 +189,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 +204,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/src/android/content/cts/AvailableIntentsTest.java b/tests/tests/content/src/android/content/cts/AvailableIntentsTest.java
index 4723524..3da8174 100644
--- a/tests/tests/content/src/android/content/cts/AvailableIntentsTest.java
+++ b/tests/tests/content/src/android/content/cts/AvailableIntentsTest.java
@@ -365,4 +365,29 @@
             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));
+    }
 }
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/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..03788b5 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;
@@ -948,4 +952,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/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..1a53a10 100644
--- a/tests/tests/database/AndroidTest.xml
+++ b/tests/tests/database/AndroidTest.xml
@@ -16,6 +16,7 @@
 <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" />
@@ -23,6 +24,5 @@
     <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/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/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/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 570a9b4..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 := android-support-test
+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/AndroidManifest.xml b/tests/tests/gesture/AndroidManifest.xml
index eed0ffd..28a2c7a 100755
--- a/tests/tests/gesture/AndroidManifest.xml
+++ b/tests/tests/gesture/AndroidManifest.xml
@@ -25,7 +25,7 @@
 
     <!--  self-instrumenting test package. -->
     <instrumentation
-        android:name="android.support.test.runner.AndroidJUnitRunner"
+        android:name="androidx.test.runner.AndroidJUnitRunner"
         android:targetPackage="android.gesture.cts"
         android:label="CTS tests of android.gesture">
     </instrumentation>
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..c24086a 100644
--- a/tests/tests/graphics/src/android/graphics/cts/BitmapColorSpaceTest.java
+++ b/tests/tests/graphics/src/android/graphics/cts/BitmapColorSpaceTest.java
@@ -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..69bb828 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;
@@ -207,8 +210,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 +228,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 +284,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 +327,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 +335,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 +405,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 +436,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 +475,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
@@ -1297,6 +1406,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);
@@ -1457,6 +1590,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 +1670,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 +1741,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 +1817,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
deleted file mode 100644
index af11307..0000000
--- a/tests/tests/graphics/src/android/graphics/cts/CanvasTest.java
+++ /dev/null
@@ -1,1780 +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.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.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;
-
-    @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());
-    }
-
-    @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
-    }
-
-    @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
-    }
-
-    @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));
-    }
-}
diff --git a/tests/tests/graphics/src/android/graphics/cts/ColorSpaceTest.java b/tests/tests/graphics/src/android/graphics/cts/ColorSpaceTest.java
index b535edb..01ba9e5 100644
--- a/tests/tests/graphics/src/android/graphics/cts/ColorSpaceTest.java
+++ b/tests/tests/graphics/src/android/graphics/cts/ColorSpaceTest.java
@@ -22,6 +22,8 @@
 import static org.junit.Assert.assertSame;
 import static org.junit.Assert.assertTrue;
 import static org.junit.Assert.fail;
+import org.junit.Rule;
+import org.junit.rules.ExpectedException;
 
 import android.graphics.ColorSpace;
 import android.support.test.filters.SmallTest;
@@ -69,6 +71,9 @@
 
     private static final DoubleUnaryOperator sIdentity = DoubleUnaryOperator.identity();
 
+    @Rule
+    public ExpectedException mExpectedException = ExpectedException.none();
+
     @Test
     public void testNamedColorSpaces() {
         for (ColorSpace.Named named : ColorSpace.Named.values()) {
@@ -610,6 +615,31 @@
     }
 
     @Test
+    public void testSinglePointAdaptation() {
+        float[] illumD65xyY = Arrays.copyOf(ColorSpace.ILLUMINANT_D65,
+                ColorSpace.ILLUMINANT_D65.length);
+        float[] illumD50xyY = Arrays.copyOf(ColorSpace.ILLUMINANT_D50,
+                ColorSpace.ILLUMINANT_D50.length);
+
+        final float[] catXyz = ColorSpace.chromaticAdaptation(ColorSpace.Adaptation.BRADFORD,
+                illumD65xyY, illumD50xyY);
+
+        // Ensure the original arguments were not modified
+        assertArrayEquals(illumD65xyY, ColorSpace.ILLUMINANT_D65, 0);
+        assertArrayEquals(illumD50xyY, ColorSpace.ILLUMINANT_D50, 0);
+
+        // Verify results. This reference data has been cross-checked with
+        // http://www.brucelindbloom.com/index.html?Eqn_ChromAdapt.html
+        final float[] illumD65ToIllumD50Xyz = {
+             1.0478525f,  0.0295722f, -0.0092367f,
+             0.0229074f,  0.9904668f,  0.0150463f,
+            -0.0501464f, -0.0170567f,  0.7520621f
+        };
+
+        assertArrayEquals(catXyz, illumD65ToIllumD50Xyz, 1e-7f);
+    }
+
+    @Test
     public void testImplicitSRGBConnector() {
         ColorSpace.Connector connector1 = ColorSpace.connect(
                 ColorSpace.get(ColorSpace.Named.DCI_P3));
@@ -783,6 +813,19 @@
                         1 / 1.055, 0.055 / 1.055, 1 / 12.92, 0.04045, 2.4)));
     }
 
+    @Test
+    public void testCctToIlluminantdXyz() {
+        assertArrayEquals(ColorSpace.cctToIlluminantdXyz(5000),
+                xyYToXyz(ColorSpace.ILLUMINANT_D50), 0.01f);
+        assertArrayEquals(ColorSpace.cctToIlluminantdXyz(7500),
+                xyYToXyz(ColorSpace.ILLUMINANT_D75), 0.01f);
+    }
+
+    @Test
+    public void testCctToIlluminantdXyzErrors() {
+        mExpectedException.expect(IllegalArgumentException.class);
+        ColorSpace.cctToIlluminantdXyz(0);
+    }
 
     @SuppressWarnings("SameParameterValue")
     private static void assertArrayNotEquals(float[] a, float[] b, float eps) {
@@ -800,4 +843,12 @@
             }
         }
     }
+
+    /**
+     * Convenience function copied from android.graphics.ColorSpace
+     */
+    private static float[] xyYToXyz(float[] xyY) {
+        return new float[] { xyY[0] / xyY[1], 1.0f, (1 - xyY[0] - xyY[1]) / xyY[1] };
+    }
+
 }
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..0cbf5d4 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,7 +1690,7 @@
         };
         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));
@@ -1705,7 +1716,7 @@
                         // We do not support 565 in HARDWARE, so no RAM savings
                         // are possible.
                         assertEquals(normalByteCount, byteCount);
-                    } else { // R.raw.basi6a16
+                    } else { // R.raw.f16
                         // 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
@@ -1723,15 +1734,10 @@
                         }
                     }
                 } 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);
-                    }
+                    // Not decoding to HARDWARE, but |normal| was. As such this should always
+                    // succeed in being smaller, as software will decode to 565 in this case.
+                    // This will always be less than whatever HARDWARE supports.
+                    assertTrue(byteCount < normalByteCount);
                 }
             }
         }
@@ -1764,8 +1770,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.
@@ -2112,10 +2118,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_EXTENDED_SRGB.
+                    // See b/117601185 and b/77276533
+                    //ColorSpace.get(ColorSpace.Named.LINEAR_SRGB),
             }) {
                 try {
                     Bitmap bm = ImageDecoder.decodeBitmap(src, (decoder, info, s) -> {
@@ -2192,6 +2199,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..caaddf2
--- /dev/null
+++ b/tests/tests/graphics/src/android/graphics/cts/InsetsTest.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.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);
+    }
+}
diff --git a/tests/tests/graphics/src/android/graphics/cts/PaintTest.java b/tests/tests/graphics/src/android/graphics/cts/PaintTest.java
index 3a539b8..9d7d510 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,6 +30,7 @@
 import android.content.Context;
 import android.graphics.Bitmap;
 import android.graphics.BitmapShader;
+import android.graphics.BlendMode;
 import android.graphics.ColorFilter;
 import android.graphics.MaskFilter;
 import android.graphics.Matrix;
@@ -34,6 +41,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,15 +51,16 @@
 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 org.junit.Test;
 import org.junit.runner.RunWith;
 
 import java.util.Locale;
 
-import com.android.compatibility.common.util.CddTest;
-
 @SmallTest
 @RunWith(AndroidJUnit4.class)
 public class PaintTest {
@@ -711,13 +721,13 @@
         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
@@ -752,8 +762,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 +929,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 +990,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 +1741,329 @@
         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);
+        }
+    }
 }
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/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..6c4b312
--- /dev/null
+++ b/tests/tests/graphics/src/android/graphics/text/cts/MeasuredTextTest.java
@@ -0,0 +1,312 @@
+/*
+ * Copyright (C) 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR 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.assertNotNull;
+
+import android.content.Context;
+import android.content.res.AssetManager;
+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 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();
+    }
+}
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..6d85626
--- /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_NO_BIOMETRICS);
+        }
+    }
+}
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/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/LocationManagerTest.java b/tests/tests/location/src/android/location/cts/LocationManagerTest.java
index d58ebb6..c5f278d 100644
--- a/tests/tests/location/src/android/location/cts/LocationManagerTest.java
+++ b/tests/tests/location/src/android/location/cts/LocationManagerTest.java
@@ -1058,40 +1058,6 @@
         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/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..11d687d 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)
 
diff --git a/tests/tests/media/AndroidManifest.xml b/tests/tests/media/AndroidManifest.xml
index eca5dd6..c4bf99f 100644
--- a/tests/tests/media/AndroidManifest.xml
+++ b/tests/tests/media/AndroidManifest.xml
@@ -100,20 +100,6 @@
                 <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">
-            <intent-filter>
-                <action android:name="android.media.MediaSessionService2" />
-            </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>
 
     <instrumentation android:name="android.support.test.runner.AndroidJUnitRunner"
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/OWNERS b/tests/tests/media/OWNERS
index 62fdd0b..1adfc35 100644
--- a/tests/tests/media/OWNERS
+++ b/tests/tests/media/OWNERS
@@ -6,3 +6,5 @@
 jaewan@google.com
 wjia@google.com
 jtinker@google.com
+dwkang@google.com
+jmtrivi@google.com
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/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/values/exifinterface.xml b/tests/tests/media/res/values/exifinterface.xml
index 23fbb93..3ab9364 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>3500</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>572</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>3143</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/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/DecoderTest.java b/tests/tests/media/src/android/media/cts/DecoderTest.java
index c1f72f8..62d2fbd 100755
--- a/tests/tests/media/src/android/media/cts/DecoderTest.java
+++ b/tests/tests/media/src/android/media/cts/DecoderTest.java
@@ -1948,6 +1948,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/ExifInterfaceTest.java b/tests/tests/media/src/android/media/cts/ExifInterfaceTest.java
index f75a6fd..39bb458 100644
--- a/tests/tests/media/src/android/media/cts/ExifInterfaceTest.java
+++ b/tests/tests/media/src/android/media/cts/ExifInterfaceTest.java
@@ -39,6 +39,7 @@
 import java.io.InputStream;
 import java.io.IOException;
 import java.lang.reflect.Type;
+import java.util.Arrays;
 
 import libcore.io.IoUtils;
 import libcore.io.Streams;
@@ -99,10 +100,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 +147,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);
@@ -251,6 +260,9 @@
         // Checks a thumbnail image.
         assertEquals(expectedValue.hasThumbnail, exifInterface.hasThumbnail());
         if (expectedValue.hasThumbnail) {
+            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 +279,17 @@
         float[] latLong = new float[2];
         assertEquals(expectedValue.hasLatLong, exifInterface.getLatLong(latLong));
         if (expectedValue.hasLatLong) {
+            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);
 
@@ -311,6 +332,10 @@
         assertNotNull(exifInterface);
         compareWithExpectedValue(exifInterface, expectedValue, verboseTag);
 
+        // Creates via file.
+        exifInterface = new ExifInterface(imageFile);
+        compareWithExpectedValue(exifInterface, expectedValue, verboseTag);
+
         InputStream in = null;
         // Creates via InputStream.
         try {
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..304f0d9 100644
--- a/tests/tests/media/src/android/media/cts/MediaCodecTest.java
+++ b/tests/tests/media/src/android/media/cts/MediaCodecTest.java
@@ -33,6 +33,7 @@
 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;
@@ -2059,4 +2060,44 @@
         pcmStream1.close();
         pcmStream2.close();
     }
+
+    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
deleted file mode 100644
index 721ddf7..0000000
--- a/tests/tests/media/src/android/media/cts/MediaController2Test.java
+++ /dev/null
@@ -1,1132 +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 org.junit.Assert.assertFalse;
-import static org.junit.Assert.assertNotEquals;
-import static org.junit.Assert.assertNotNull;
-import static org.junit.Assert.assertNull;
-import static org.junit.Assert.assertTrue;
-import static org.junit.Assert.fail;
-
-import android.app.PendingIntent;
-import android.content.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.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.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.lang.reflect.Method;
-import java.util.List;
-import java.util.concurrent.CountDownLatch;
-import java.util.concurrent.TimeUnit;
-import java.util.concurrent.atomic.AtomicReference;
-
-/**
- * Tests {@link 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";
-
-    PendingIntent mIntent;
-    MediaSession2 mSession;
-    MediaController2 mController;
-    MockPlayer mPlayer;
-    MockPlaylistAgent mMockAgent;
-
-    @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();
-        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);
-    }
-
-    @After
-    @Override
-    public void cleanUp() throws Exception {
-        super.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));
-        } 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();
-            }
-        }
-    }
-
-    @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());
-    }
-
-    @Test
-    public void testConnectToService_sessionService() throws InterruptedException {
-        testConnectToService(MockMediaSessionService2.ID);
-    }
-
-    @Ignore
-    @Test
-    public void testConnectToService_libraryService() throws InterruptedException {
-        testConnectToService(MockMediaLibraryService2.ID);
-    }
-
-    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);
-            }
-        };
-        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));
-        */
-    }
-
-    @Test
-    public void testControllerAfterSessionIsGone_session() throws InterruptedException {
-        testControllerAfterSessionIsGone(mSession.getToken().getId());
-    }
-
-    // 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 void onSetVolumeTo(int volume) {
-            mSetVolumeToCalled = true;
-            mVolume = volume;
-            mLatch.countDown();
-        }
-
-        @Override
-        public void onAdjustVolume(int direction) {
-            mAdjustVolumeCalled = true;
-            mDirection = direction;
-            mLatch.countDown();
-        }
-    }
-}
diff --git a/tests/tests/media/src/android/media/cts/MediaDrmClearkeyTest.java b/tests/tests/media/src/android/media/cts/MediaDrmClearkeyTest.java
index c504c4b..ec2be4d 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,6 +102,7 @@
             new UUID(0xe2719d58a985b3c9L, 0x781ab030af78d30eL);
 
     private byte[] mDrmInitData;
+    private byte[] mKeySetId;
     private byte[] mSessionId;
     private Looper mLooper;
     private MediaCodecClearKeyPlayer mMediaCodecPlayer;
@@ -166,7 +168,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 +177,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 +191,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 +211,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 +240,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,10 +274,12 @@
                                     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);
                                 }
@@ -350,32 +360,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 +391,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 +433,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 +469,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 +562,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 +585,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 +600,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_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 +693,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 +705,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 +728,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 +739,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 +750,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 +808,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 +853,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 +933,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 +1052,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 +1082,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();
diff --git a/tests/tests/media/src/android/media/cts/MediaExtractorTest.java b/tests/tests/media/src/android/media/cts/MediaExtractorTest.java
index 7dc9130..f5a6393 100644
--- a/tests/tests/media/src/android/media/cts/MediaExtractorTest.java
+++ b/tests/tests/media/src/android/media/cts/MediaExtractorTest.java
@@ -16,21 +16,31 @@
 
 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.AudioPresentation;
 import android.media.MediaDataSource;
 import android.media.MediaExtractor;
+import android.media.MediaFormat;
 import android.os.PersistableBundle;
 import android.test.AndroidTestCase;
+import android.util.Log;
 
 import java.io.IOException;
 import java.nio.ByteBuffer;
+import java.util.Arrays;
+import java.util.HashMap;
 import java.util.List;
+import java.util.Map;
 
 public class MediaExtractorTest extends AndroidTestCase {
+    private static final String TAG = "MediaExtractorTest";
+
     protected Resources mResources;
     protected MediaExtractor mExtractor;
 
@@ -125,10 +135,121 @@
 
     }
 
+    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);
+        // See b/120846068, the call to 'seek' is needed to guarantee a reset of the AP parser.
+        mExtractor.seekTo(0, MediaExtractor.SEEK_TO_CLOSEST_SYNC);
+        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]);
+        }
     }
 }
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..375fae9 100644
--- a/tests/tests/media/src/android/media/cts/MediaMetadataRetrieverTest.java
+++ b/tests/tests/media/src/android/media/cts/MediaMetadataRetrieverTest.java
@@ -613,11 +613,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/MediaPlayer2DrmTest.java b/tests/tests/media/src/android/media/cts/MediaPlayer2DrmTest.java
index a4c5c4c..3143e48 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,
diff --git a/tests/tests/media/src/android/media/cts/MediaPlayer2DrmTestBase.java b/tests/tests/media/src/android/media/cts/MediaPlayer2DrmTestBase.java
index 314c051..9f083f2 100644
--- a/tests/tests/media/src/android/media/cts/MediaPlayer2DrmTestBase.java
+++ b/tests/tests/media/src/android/media/cts/MediaPlayer2DrmTestBase.java
@@ -15,46 +15,30 @@
  */
 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.VideoSize;
 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.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;
@@ -67,11 +51,14 @@
 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();
@@ -87,56 +74,37 @@
 
     protected MediaPlayer2 mPlayer = null;
     protected MediaStubActivity mActivity;
-    protected Instrumentation mInstrumentation;
 
     protected ExecutorService mExecutor;
     protected MediaPlayer2.EventCallback mECb = null;
 
-    @Rule
-    public ActivityTestRule<MediaStubActivity> mActivityRule =
-            new ActivityTestRule<>(MediaStubActivity.class);
-    public PowerManager.WakeLock mScreenLock;
-    private KeyguardManager mKeyguardManager;
+    public MediaPlayer2DrmTestBase() {
+        super(MediaStubActivity.class);
+    }
 
-    @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);
-            }
-        });
-        mInstrumentation.waitForIdleSync();
-
+    @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;
@@ -274,8 +242,9 @@
         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);
+                public void onVideoSizeChanged(MediaPlayer2 mp, DataSourceDesc dsd, VideoSize size) {
+                    Log.v(TAG, "VideoSizeChanged" + " w:" + size.getWidth()
+                            + " h:" + size.getHeight());
                     mOnVideoSizeChangedCalled.signal();
                 }
 
@@ -288,7 +257,7 @@
                 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) {
+                    } else if (what == MediaPlayer2.MEDIA_INFO_DATA_SOURCE_END) {
                         Log.v(TAG, "playLoadedVideo: onInfo_PlaybackComplete");
                         mOnPlaybackCompleted.signal();
                     }
@@ -304,9 +273,10 @@
                 }
             };
 
-        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,19 +289,19 @@
         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;
             }
 
@@ -358,14 +328,14 @@
 
         try {
             Log.v(TAG, "playLoadedVideo: releaseDrm");
-            mPlayer.releaseDrm();
+            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 +343,32 @@
             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() {
+        mPlayer.registerDrmEventCallback(mExecutor, new MediaPlayer2.DrmEventCallback() {
             @Override
-            public void onDrmInfo(MediaPlayer2 mp, DataSourceDesc dsd, DrmInfo drmInfo) {
+            public void 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;
+                }
 
                 // 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);
@@ -417,19 +393,27 @@
         }
     }
 
-    private void preparePlayerAndDrm_V2_syncDrmSetupPlusConfig() throws Exception {
+    private void preparePlayerAndDrm_V2_syncDrmSetupPlusConfig(DataSourceDesc dsd)
+            throws Exception {
+        final AtomicBoolean drmConfigError = new AtomicBoolean(false);
         mPlayer.setOnDrmConfigHelper(new MediaPlayer2.OnDrmConfigHelper() {
             @Override
-            public void onDrmConfig(MediaPlayer2 mp, DataSourceDesc dsd) {
+            public void onDrmConfig(MediaPlayer2 mp, DataSourceDesc dsd2) {
+                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 = mp.getDrmPropertyString(dsd2, securityLevelProperty);
                     Log.v(TAG, "preparePlayerAndDrm_V2: getDrmPropertyString: "
                             + securityLevelProperty + " -> " + level);
-                    mp.setDrmPropertyString(securityLevelProperty, widevineSecurityLevel3);
-                    level = mp.getDrmPropertyString(securityLevelProperty);
+                    mp.setDrmPropertyString(dsd2, securityLevelProperty, widevineSecurityLevel3);
+                    level = mp.getDrmPropertyString(dsd2, securityLevelProperty);
                     Log.v(TAG, "preparePlayerAndDrm_V2: getDrmPropertyString: "
                             + securityLevelProperty + " -> " + level);
                 } catch (MediaPlayer2.NoDrmSchemeException e) {
@@ -448,27 +432,32 @@
             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() {
+        mPlayer.registerDrmEventCallback(mExecutor, new MediaPlayer2.DrmEventCallback() {
             @Override
-            public void onDrmInfo(MediaPlayer2 mp, DataSourceDesc dsd, DrmInfo drmInfo) {
+            public void 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
@@ -481,37 +470,35 @@
                 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);
-                    asyncSetupDrmError.set(true);
-                    mOnDrmInfoCalled.signal();
-                    // need to get passed the wait
-                    mOnDrmPreparedCalled.signal();
-                    return;
-                }
+                Log.v(TAG, "preparePlayerAndDrm_V3: onDrmInfo: calling prepareDrm");
+                mp.prepareDrm(dsd2, drmScheme);
 
                 mOnDrmInfoCalled.signal();
                 Log.v(TAG, "preparePlayerAndDrm_V3: onDrmInfo done!");
             }
 
             @Override
-            public void onDrmPrepared(MediaPlayer2 mp, DataSourceDesc dsd, int status) {
+            public void onDrmPrepared(MediaPlayer2 mp, DataSourceDesc dsd2, int status) {
                 Log.v(TAG, "preparePlayerAndDrm_V3: onDrmPrepared status: " + status);
 
-                assertTrue("preparePlayerAndDrm_V3: onDrmPrepared did not succeed",
-                           status == MediaPlayer2.PREPARE_DRM_STATUS_SUCCESS);
+                if (dsd != dsd2) {
+                    asyncSetupDrmError.set(true);
+                    Log.e(TAG, "preparePlayerAndDrm_V3: onDrmPrepared dsd mismatch");
+                    mOnDrmPreparedCalled.signal();
+                    return;
+                }
 
-                DrmInfo drmInfo = mPlayer.getDrmInfo();
+                if (status != MediaPlayer2.PREPARE_DRM_STATUS_SUCCESS) {
+                    asyncSetupDrmError.set(true);
+                    Log.e(TAG, "preparePlayerAndDrm_V3: onDrmPrepared did not succeed");
+                }
+
+                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);
@@ -556,26 +543,27 @@
             boolean restoreRound = (round == 1);
             Log.v(TAG, "playLoadedVideo: round " + round);
 
+            UriDataSourceDesc.Builder dsdBuilder = new UriDataSourceDesc.Builder();
+            DataSourceDesc dsd = dsdBuilder.setDataSource(mContext, file).build();
             try {
-                mPlayer.setEventCallback(mExecutor, mECb);
+                mPlayer.registerEventCallback(mExecutor, mECb);
 
                 Log.v(TAG, "playLoadedVideo: setDataSource()");
-                mPlayer.setDataSource(
-                        new DataSourceDesc.Builder().setDataSource(mContext, file).build());
+                mPlayer.setDataSource(dsd);
 
                 Log.v(TAG, "playLoadedVideo: prepare()");
                 mPlayer.prepare();
                 mOnPreparedCalled.waitForSignal();
 
                 // but preparing the DRM every time with proper key request type
-                drmInfo = mPlayer.getDrmInfo();
+                drmInfo = mPlayer.getDrmInfo(dsd);
                 if (drmInfo != null) {
                     if (keyRequestRound) {
                         // asking for offline keys
-                        setupDrm(drmInfo, true /* prepareDrm */, true /* synchronousNetworking */,
-                                 MediaDrm.KEY_TYPE_OFFLINE);
+                        setupDrm(dsd, drmInfo, true /* prepareDrm */,
+                                 true /* synchronousNetworking */, MediaDrm.KEY_TYPE_OFFLINE);
                     } else if (restoreRound) {
-                        setupDrmRestore(drmInfo, true /* prepareDrm */);
+                        setupDrmRestore(dsd, drmInfo, true /* prepareDrm */);
                     } else {
                         fail("preparePlayer: unexpected round " + round);
                     }
@@ -607,13 +595,13 @@
                 if (drmInfo != null) {
                     if (restoreRound) {
                         // releasing the offline key
-                        setupDrm(null /* drmInfo */, false /* prepareDrm */,
+                        setupDrm(dsd, null /* drmInfo */, false /* prepareDrm */,
                                  true /* synchronousNetworking */, MediaDrm.KEY_TYPE_RELEASE);
                         Log.v(TAG, "playLoadedVideo: released offline keys");
                     }
 
                     Log.v(TAG, "playLoadedVideo: releaseDrm");
-                    mPlayer.releaseDrm();
+                    mPlayer.releaseDrm(dsd);
                 }
             } catch (Exception e) {
                 e.printStackTrace();
@@ -667,14 +655,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 +679,24 @@
                     Log.d(TAG, "setupDrm: selected " + drmScheme);
 
                     if (prepareDrm) {
-                        mPlayer.prepareDrm(drmScheme);
+                        final Monitor drmPrepared = new Monitor();
+                        mPlayer.registerDrmEventCallback(
+                                mExecutor, new MediaPlayer2.DrmEventCallback() {
+                            @Override
+                            public void onDrmPrepared(
+                                    MediaPlayer2 mp, DataSourceDesc dsd2, int status) {
+                                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 +731,7 @@
             }
 
             final MediaDrm.KeyRequest request = mPlayer.getDrmKeyRequest(
+                    dsd,
                     (keyType == MediaDrm.KEY_TYPE_RELEASE) ? mKeySetId : null,
                     initData,
                     mime,
@@ -741,6 +748,7 @@
 
             // 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 +759,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 +766,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 +783,31 @@
                 UUID drmScheme = CLEARKEY_SCHEME_UUID;
                 Log.d(TAG, "setupDrmRestore: selected " + drmScheme);
 
-                mPlayer.prepareDrm(drmScheme);
+                final Monitor drmPrepared = new Monitor();
+                mPlayer.registerDrmEventCallback(
+                        mExecutor, new MediaPlayer2.DrmEventCallback() {
+                    @Override
+                    public void onDrmPrepared(
+                            MediaPlayer2 mp, DataSourceDesc dsd2, int status) {
+                        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");
diff --git a/tests/tests/media/src/android/media/cts/MediaPlayer2Test.java b/tests/tests/media/src/android/media/cts/MediaPlayer2Test.java
index 2538cea..a4401221 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;
@@ -37,6 +41,7 @@
 import android.media.SubtitleData;
 import android.media.SyncParams;
 import android.media.TimedText;
+import android.media.VideoSize;
 import android.media.audiofx.AudioEffect;
 import android.media.audiofx.Visualizer;
 import android.media.cts.TestUtils.Monitor;
@@ -47,20 +52,30 @@
 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.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();
@@ -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();
@@ -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();
             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();
 
@@ -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.testvideo;
+        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.testvideo;
+        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.testvideo;
+        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, "testPlaylist: MEDIA_INFO_DATA_SOURCE_START dsd2");
+                        onStart2Called.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 {
+                        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();
+                }
+            }
+
+            @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();
         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,225 +1603,150 @@
 
     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);
     }
@@ -1609,16 +1787,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 +1814,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 +1841,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 +1874,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 +1894,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 +1908,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 +1921,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 +1944,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 +1973,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 +1986,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 +2007,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 +2028,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 +2050,14 @@
         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.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 +2084,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,7 +2101,7 @@
                 }
             }
         };
-        mPlayer.setMediaPlayer2EventCallback(mExecutor, ecb);
+        mPlayer.registerEventCallback(mExecutor, ecb);
 
         mOnPrepareCalled.reset();
         mPlayer.prepare();
@@ -1941,7 +2113,7 @@
         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 +2124,6 @@
     }
 
     public void testCallback() throws Throwable {
-        if (IGNORE_TESTS) {
-            return;
-        }
         final int mp4Duration = 8484;
 
         if (!checkLoadResource(R.raw.testvideo)) {
@@ -1965,10 +2134,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, VideoSize size) {
                 mOnVideoSizeChangedCalled.signal();
             }
 
@@ -1983,7 +2151,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 +2182,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 +2207,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 +2269,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 +2278,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 +2308,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 +2330,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 +2350,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 +2368,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 +2379,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 +2413,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 +2446,361 @@
         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, VideoSize 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());
+    }
 }
diff --git a/tests/tests/media/src/android/media/cts/MediaPlayer2TestBase.java b/tests/tests/media/src/android/media/cts/MediaPlayer2TestBase.java
index 2975d47..6f88364 100644
--- a/tests/tests/media/src/android/media/cts/MediaPlayer2TestBase.java
+++ b/tests/tests/media/src/android/media/cts/MediaPlayer2TestBase.java
@@ -22,12 +22,17 @@
 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.VideoSize;
 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;
@@ -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, VideoSize 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, VideoSize 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/MediaScannerTest.java b/tests/tests/media/src/android/media/cts/MediaScannerTest.java
index 16034e4..913e61e 100644
--- a/tests/tests/media/src/android/media/cts/MediaScannerTest.java
+++ b/tests/tests/media/src/android/media/cts/MediaScannerTest.java
@@ -16,8 +16,6 @@
 
 package android.media.cts;
 
-import android.media.cts.R;
-
 import android.content.ComponentName;
 import android.content.ContentResolver;
 import android.content.ContentUris;
@@ -30,17 +28,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.test.AndroidTestCase;
+import android.test.suitebuilder.annotation.SmallTest;
 import android.util.Log;
 
 import com.android.compatibility.common.util.FileCopyHelper;
@@ -210,12 +207,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 +215,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
diff --git a/tests/tests/media/src/android/media/cts/MediaSession2Test.java b/tests/tests/media/src/android/media/cts/MediaSession2Test.java
deleted file mode 100644
index 0f2c56b..0000000
--- a/tests/tests/media/src/android/media/cts/MediaSession2Test.java
+++ /dev/null
@@ -1,876 +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.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.os.Bundle;
-import android.os.Process;
-import android.os.ResultReceiver;
-import android.platform.test.annotations.AppModeFull;
-import androidx.annotation.NonNull;
-import android.support.test.filters.SmallTest;
-import android.support.test.runner.AndroidJUnit4;
-
-import junit.framework.Assert;
-
-import org.junit.After;
-import org.junit.Before;
-import org.junit.Ignore;
-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.TimeUnit;
-
-/**
- * Tests {@link 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";
-
-    private MediaSession2 mSession;
-    private MockPlayer mPlayer;
-    private MockPlaylistAgent mMockAgent;
-
-    @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();
-    }
-
-    @After
-    @Override
-    public void cleanUp() throws Exception {
-        super.cleanUp();
-        mSession.close();
-    }
-
-    @Ignore
-    @Test
-    public void testBuilder() {
-        try {
-            MediaSession2.Builder builder = new Builder(mContext);
-            fail("null player shouldn't be allowed");
-        } catch (IllegalArgumentException e) {
-            // expected. pass-through
-        }
-        MediaSession2.Builder builder = new Builder(mContext).setPlayer(mPlayer);
-        try {
-            builder.setId(null);
-            fail("null id shouldn't be allowed");
-        } catch (IllegalArgumentException e) {
-            // expected. pass-through
-        }
-    }
-
-    @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)
-        }
-    }
-
-    @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)
-        }
-    }
-
-    @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)
-        }
-    }
-
-    @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")
-                .setSessionCallback(sHandlerExecutor, sessionCallback)
-                .build()) {
-            agent.notifyPlaylistChanged();
-            assertTrue(latch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS));
-        }
-    }
-
-    @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());
-            controller.close();
-            assertTrue(latch.await(WAIT_TIME_MS, TimeUnit.MILLISECONDS));
-        }
-    }
-
-    @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);
-            }
-        };
-
-        try (final MediaSession2 session = new MediaSession2.Builder(mContext)
-                .setPlayer(mPlayer)
-                .setId("testSetCustomLayout")
-                .setSessionCallback(sHandlerExecutor, sessionCallback)
-                .build()) {
-            if (mSession != null) {
-                mSession.close();
-                mSession = session;
-            }
-            final ControllerCallback callback = new ControllerCallback() {
-                @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();
-                }
-            };
-            final MediaController2 controller =
-                    createController(session.getToken(), true, callback);
-            assertTrue(latch.await(WAIT_TIME_MS, TimeUnit.MILLISECONDS));
-        }
-    }
-
-    @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));
-
-        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();
-
-                assertNotNull(actual);
-                assertEquals(expected.size(), actual.size());
-                for (SessionCommand2 command : expected) {
-                    assertTrue(actual.contains(command));
-                }
-                latch.countDown();
-            }
-        };
-
-        final MediaController2 controller = createController(mSession.getToken(), true, callback);
-        ControllerInfo controllerInfo = getTestControllerInfo();
-        assertNotNull(controllerInfo);
-
-        mSession.setAllowedCommands(controllerInfo, commands);
-        assertTrue(latch.await(WAIT_TIME_MS, TimeUnit.MILLISECONDS));
-    }
-
-    @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;
-        }
-    }
-
-    public class MockOnCommandCallback extends SessionCallback {
-        public final ArrayList<SessionCommand2> commands = new ArrayList<>();
-
-        @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) {
-                return false;
-            }
-            return true;
-        }
-    }
-
-    private static void assertMediaItemListEquals(List<MediaItem2> a, List<MediaItem2> b) {
-        if (a == null || b == null) {
-            assertEquals(a, b);
-        }
-        assertEquals(a.size(), b.size());
-
-        for (int i = 0; i < a.size(); i++) {
-            MediaItem2 aItem = a.get(i);
-            MediaItem2 bItem = b.get(i);
-
-            if (aItem == null || bItem == null) {
-                assertEquals(aItem, bItem);
-                continue;
-            }
-
-            assertEquals(aItem.getMediaId(), bItem.getMediaId());
-            assertEquals(aItem.getFlags(), bItem.getFlags());
-            TestUtils.equals(aItem.getMetadata().toBundle(), bItem.getMetadata().toBundle());
-
-            // Note: Here it does not check whether DataSourceDesc are equal,
-            // since there DataSourceDec is not comparable.
-        }
-    }
-}
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/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/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/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..5e4f598 100644
--- a/tests/tests/media/src/android/media/cts/NativeDecoderTest.java
+++ b/tests/tests/media/src/android/media/cts/NativeDecoderTest.java
@@ -27,6 +27,7 @@
 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;
@@ -44,13 +45,25 @@
 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;
 
 @AppModeFull(reason = "TODO: evaluate and port to instant")
 public class NativeDecoderTest extends MediaPlayerTestBase {
@@ -85,6 +98,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 +135,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 +184,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 +248,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 +262,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 +300,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 +336,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 +367,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 +428,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 +527,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 +776,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 69e3f5c..75e0d5b 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
@@ -733,6 +742,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/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..733fc7e 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,7 +678,8 @@
             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();
@@ -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/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/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/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..b10997c 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 "";
 }
@@ -851,10 +852,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 +890,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;
 }
 
@@ -1017,11 +1044,6 @@
             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());
 }
 
@@ -1674,8 +1696,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 +1717,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},
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_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/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/cts/WifiManagerTest.java b/tests/tests/net/src/android/net/wifi/cts/WifiManagerTest.java
index af91fbf..deaa644 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,6 +54,7 @@
 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;
@@ -60,6 +70,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 +85,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 +92,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 +167,8 @@
         mWifiLock.acquire();
         if (!mWifiManager.isWifiEnabled())
             setWifiEnabled(true);
+        mUiDevice = UiDevice.getInstance(InstrumentationRegistry.getInstrumentation());
+        turnScreenOnNoDelay();
         Thread.sleep(DURATION);
         assertTrue(mWifiManager.isWifiEnabled());
         synchronized (mMySync) {
@@ -186,8 +199,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 +275,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 +402,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 +726,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 +777,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 +847,238 @@
 
         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 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);
+
+        // 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 +"]");
+            }
+        }
+    }
+
+    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/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..6b52e7a
--- /dev/null
+++ b/tests/tests/notificationlegacy/notificationlegacy29/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.legacy29.cts">
+
+    <uses-sdk android:minSdkVersion="29"  />
+    <application>
+        <uses-library android:name="android.test.runner" />
+
+    </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/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/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/os/Android.mk b/tests/tests/os/Android.mk
index 5ab51d8..1fb4056 100644
--- a/tests/tests/os/Android.mk
+++ b/tests/tests/os/Android.mk
@@ -47,9 +47,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/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..5f5441b
--- /dev/null
+++ b/tests/tests/os/src/android/os/cts/PowerManager_ThermalTest.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.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 org.junit.After;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+
+import java.io.IOException;
+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);
+        mUiDevice.executeShellCommand("cmd thermalservice override-status 0");
+    }
+
+    @After
+    public void tearDown() throws Exception {
+        mUiDevice.executeShellCommand("cmd thermalservice reset");
+    }
+
+    @Test
+    public void testGetThermalStatus() throws Exception {
+        int status = 0;
+        assertEquals(status, mPowerManager.getCurrentThermalStatus());
+        status = 3;
+        mUiDevice.executeShellCommand("cmd thermalservice override-status "
+                + status);
+        assertEquals(status, mPowerManager.getCurrentThermalStatus());
+    }
+
+    @Test
+    public void testThermalStatusCallback() throws Exception {
+        // Initial override status is 0
+        int status = 0;
+        mPowerManager.registerThermalStatusCallback(mCallback, mExec);
+        verify(mCallback, timeout(CALLBACK_TIMEOUT_MILLI_SEC)
+                .times(1)).onStatusChange(status);
+        reset(mCallback);
+        status = 3;
+        mUiDevice.executeShellCommand("cmd thermalservice override-status "
+                + status);
+        verify(mCallback, timeout(CALLBACK_TIMEOUT_MILLI_SEC)
+                .times(1)).onStatusChange(status);
+        reset(mCallback);
+        mPowerManager.unregisterThermalStatusCallback(mCallback);
+        status = 2;
+        mUiDevice.executeShellCommand("cmd thermalservice override-status "
+                + 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..dcdb795 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 -->
+    <uses-permission android:name="android.permission.READ_CONTACTS"
+      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..9bc70fc 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,15 @@
     <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="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 +54,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..e1917e0
--- /dev/null
+++ b/tests/tests/permission/AppThatAccessesLocationOnCommand/src/android/permission/cts/appthataccesseslocation/AccessLocationOnCommand.java
@@ -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.
+ */
+
+package android.permission.cts.appthataccesseslocation;
+
+import static android.location.LocationManager.GPS_PROVIDER;
+
+import android.app.Service;
+import android.content.Intent;
+import android.location.LocationManager;
+import android.os.Binder;
+import android.os.Handler;
+import android.os.IBinder;
+
+public class AccessLocationOnCommand extends Service {
+    private static final long DELAY_MILLIS = 100;
+
+    @Override
+    public IBinder onBind(Intent intent) {
+        return new Binder();
+    }
+
+    @Override
+    public boolean onUnbind(Intent intent) {
+        return (new Handler()).postDelayed(
+                () -> getSystemService(LocationManager.class).getLastKnownLocation(GPS_PROVIDER),
+                DELAY_MILLIS);
+    }
+}
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/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
index 745d859..0f3ed4e 100644
--- a/tests/tests/permission/src/android/permission/cts/AppOpsTest.java
+++ b/tests/tests/permission/src/android/permission/cts/AppOpsTest.java
@@ -16,6 +16,14 @@
 
 package android.permission.cts;
 
+import static com.android.compatibility.common.util.SystemUtil.runWithShellPermissionIdentity;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
+
 import static android.app.AppOpsManager.MODE_ALLOWED;
 import static android.app.AppOpsManager.MODE_DEFAULT;
 import static android.app.AppOpsManager.MODE_ERRORED;
@@ -35,20 +43,28 @@
 
 import android.Manifest.permission;
 import android.app.AppOpsManager;
+import android.app.AppOpsManager.HistoricalOpEntry;
+import android.app.AppOpsManager.HistoricalPackageOps;
 import android.app.AppOpsManager.OnOpChangedListener;
+import android.app.Instrumentation;
 import android.content.Context;
 import android.os.Process;
-import android.test.InstrumentationTestCase;
-import android.test.suitebuilder.annotation.SmallTest;
+import android.support.test.runner.AndroidJUnit4;
+import android.support.test.InstrumentationRegistry;
 
 import com.android.compatibility.common.util.AppOpsUtils;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
 
 import java.util.HashMap;
 import java.util.HashSet;
+import java.util.List;
 import java.util.Map;
 import java.util.Set;
 
-public class AppOpsTest extends InstrumentationTestCase {
+@RunWith(AndroidJUnit4.class)
+public class AppOpsTest {
     // Notifying OnOpChangedListener callbacks is an async operation, so we define a timeout.
     private static final int MODE_WATCHER_TIMEOUT_MS = 5000;
 
@@ -98,9 +114,8 @@
                 AppOpsManager.OPSTR_WRITE_EXTERNAL_STORAGE);
     }
 
-    @Override
-    protected void setUp() throws Exception {
-        super.setUp();
+    @Before
+    public void setUp() throws Exception {
         mContext = getInstrumentation().getContext();
         mAppOps = (AppOpsManager) mContext.getSystemService(Context.APP_OPS_SERVICE);
         mOpPackageName = mContext.getOpPackageName();
@@ -111,6 +126,7 @@
         AppOpsUtils.reset(mOpPackageName);
     }
 
+    @Test
     public void testNoteOpAndCheckOp() throws Exception {
         setOpMode(mOpPackageName, OPSTR_READ_SMS, MODE_ALLOWED);
         assertEquals(MODE_ALLOWED, mAppOps.noteOp(OPSTR_READ_SMS, mMyUid, mOpPackageName));
@@ -145,6 +161,7 @@
         }
     }
 
+    @Test
     public void testStartOpAndFinishOp() throws Exception {
         setOpMode(mOpPackageName, OPSTR_READ_SMS, MODE_ALLOWED);
         assertEquals(MODE_ALLOWED, mAppOps.startOp(OPSTR_READ_SMS, mMyUid, mOpPackageName));
@@ -169,11 +186,13 @@
         }
     }
 
+    @Test
     public void testCheckPackagePassesCheck() throws Exception {
         mAppOps.checkPackage(mMyUid, mOpPackageName);
         mAppOps.checkPackage(Process.SYSTEM_UID, "android");
     }
 
+    @Test
     public void testCheckPackageDoesntPassCheck() throws Exception {
         try {
             // Package name doesn't match UID.
@@ -197,6 +216,7 @@
         }
     }
 
+    @Test
     public void testWatchingMode() throws Exception {
         OnOpChangedListener watcher = mock(OnOpChangedListener.class);
         try {
@@ -234,7 +254,17 @@
         }
     }
 
-    @SmallTest
+    @Test
+    public void testAllOpsHaveOpString() {
+        Set<String> opStrs = new HashSet<>();
+        for (String opStr : 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
     public void testOpCodesUnique() {
         String[] opStrs = AppOpsManager.getOpStrs();
         Set<Integer> opCodes = new HashSet<>();
@@ -244,7 +274,7 @@
         assertEquals("Not all app op codes are unique", opStrs.length, opCodes.size());
     }
 
-    @SmallTest
+    @Test
     public void testPermissionMapping() {
         for (String permission : permissionToOpStr.keySet()) {
             testPermissionMapping(permission, permissionToOpStr.get(permission));
@@ -267,7 +297,7 @@
     /**
      * Test that the app can not change the app op mode for itself.
      */
-    @SmallTest
+    @Test
     public void testCantSetModeForSelf() {
         try {
             int writeSmsOp = AppOpsManager.permissionToOpCode("android.permission.WRITE_SMS");
@@ -277,7 +307,7 @@
         }
     }
 
-    @SmallTest
+    @Test
     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
@@ -302,4 +332,145 @@
         assertTrue(mustBeLogged, allowedOperationLogged(mOpPackageName, OPSTR_RECORD_AUDIO));
         assertTrue(mustBeLogged, rejectedOperationLogged(mOpPackageName, OPSTR_READ_CALENDAR));
     }
+
+    @Test
+    public void testGetHistoricalPackageOps() {
+        final AppOpsManager appOpsManager = getContext().getSystemService(AppOpsManager.class);
+        final int uid = Process.myUid();
+        final String packageName = getContext().getPackageName();
+        final String[] interestingOps = new String[] {AppOpsManager.OPSTR_FINE_LOCATION};
+
+        runWithShellPermissionIdentity(() -> {
+            // Note an op to have some data
+            appOpsManager.noteOp(AppOpsManager.OPSTR_FINE_LOCATION, uid, packageName);
+
+            // Get all ops for the package
+            final HistoricalPackageOps historicalPackageOpsForAllOps = appOpsManager
+                    .getHistoricalPackagesOps(uid, packageName, null, 0, 1);
+            // Get only the interesting ops for the package
+            final HistoricalPackageOps historicalPackageOpsForOneOp = appOpsManager
+                    .getHistoricalPackagesOps(uid, packageName, interestingOps, 0, 1);
+            // Make sure we get the same result when fetching all vs a subset
+            assertHistoricEntriesEqual(historicalPackageOpsForAllOps,
+                    historicalPackageOpsForOneOp, interestingOps);
+
+//            TODO: Uncomment once hisotric support implemented
+//            // Note an op to change
+//            appOpsManager.noteOp(AppOpsManager.OPSTR_FINE_LOCATION, uid, packageName);
+//
+//            // Get all ops for the package
+//            final HistoricalPackageOps historicalPackageOpsForAllOpsIncr = appOpsManager
+//                    .getHistoricalPackagesOps(uid, packageName, null, 0, 1);
+//            // Get only the interesting ops for the package
+//            final HistoricalPackageOps historicalPackageOpsForOneOpIncr = appOpsManager
+//                    .getHistoricalPackagesOps(uid, packageName, interestingOps, 0, 1);
+//            // Make sure we get the same result when fetching all vs a subset
+//            assertHistoricEntriesEqual(historicalPackageOpsForAllOpsIncr,
+//                    historicalPackageOpsForOneOpIncr, interestingOps);
+//
+//            // Make sure the op was incremented.
+//            assertSame(historicalPackageOpsForAllOps.getEntry(
+//                            AppOpsManager.OPSTR_FINE_LOCATION).getForegroundAccessCount() + 1,
+//                    historicalPackageOpsForAllOpsIncr.getEntry(
+//                            AppOpsManager.OPSTR_FINE_LOCATION).getForegroundAccessCount());
+        });
+    }
+
+    @Test
+    public void testGetAllHistoricPackageOps() {
+        final AppOpsManager appOpsManager = getContext().getSystemService(AppOpsManager.class);
+        final int uid = Process.myUid();
+        final String packageName = getContext().getPackageName();
+        final String[] interestingOps = new String[] {AppOpsManager.OPSTR_FINE_LOCATION};
+
+        runWithShellPermissionIdentity(() -> {
+            // Note an op to have some data
+            appOpsManager.noteOp(AppOpsManager.OPSTR_FINE_LOCATION, uid, packageName);
+
+            // Get all ops for the package
+            final List<HistoricalPackageOps> historicAllPackageOpsForAllOps = appOpsManager
+                    .getAllHistoricPackagesOps(null, 0, 1);
+            // Get only the interesting ops for the package
+            final List<AppOpsManager.HistoricalPackageOps> historicAllPackageOpsForOneOp =
+                    appOpsManager.getAllHistoricPackagesOps(interestingOps, 0, 1);
+            // Make sure we get the same result when fetching all vs a subset
+            assertHistoricEntriesEqual(historicAllPackageOpsForAllOps,
+                    historicAllPackageOpsForOneOp, interestingOps);
+
+//            TODO: Uncomment once hisotric support implemented
+//            // Note an op to change
+//            appOpsManager.noteOp(AppOpsManager.OPSTR_FINE_LOCATION, uid, packageName);
+//
+//            // Get all ops for the package
+//            final List<HistoricalPackageOps> historicAllPackageOpsForAllOpsIncr = appOpsManager
+//                    .getAllHistoricPackagesOps(null, 0, 1);
+//            // Get only the interesting ops for the package
+//            final List<HistoricalPackageOps> historicAllPackageOpsForOneOpIncr = appOpsManager
+//                    .getAllHistoricPackagesOps(interestingOps, 0, 1);
+//            // Make sure we get the same result when fetching all vs a subset
+//            assertHistoricEntriesEqual(historicAllPackageOpsForAllOpsIncr,
+//                    historicAllPackageOpsForOneOpIncr, interestingOps);
+//
+//            // Make sure the op was incremented.
+//            final HistoricalPackageOps historicalPackageOpsForOneOp = findHistoricalPackageOps(
+//                    packageName, historicAllPackageOpsForOneOp);
+//            final HistoricalPackageOps historicalPackageOpsForOneOpIncr =
+//                    findHistoricalPackageOps(packageName, historicAllPackageOpsForOneOpIncr);
+//
+//            assertSame(historicalPackageOpsForOneOp.getEntry(
+//                            AppOpsManager.OPSTR_FINE_LOCATION).getForegroundAccessCount() + 1,
+//                    historicalPackageOpsForOneOpIncr.getEntry(
+//                            AppOpsManager.OPSTR_FINE_LOCATION).getForegroundAccessCount());
+        });
+    }
+
+    private static AppOpsManager.HistoricalPackageOps findHistoricalPackageOps(
+             String packageName, List<AppOpsManager.HistoricalPackageOps> historicalPackageOps) {
+        final int packageCount = historicalPackageOps.size();
+        for (int i = 0; i < packageCount; i++) {
+            final HistoricalPackageOps packageOps = historicalPackageOps.get(i);
+            if (packageOps.getPackageName().equals(packageName)) {
+                return packageOps;
+            }
+        }
+        return null;
+    }
+
+    private static void assertHistoricEntriesEqual(
+            List<AppOpsManager.HistoricalPackageOps> expected,
+            List<AppOpsManager.HistoricalPackageOps> actual, String[] opNames) {
+        assertEquals(expected.size(), actual.size());
+
+        final int packageCount = expected.size();
+        for (int i = 0; i < packageCount; i++) {
+            final HistoricalPackageOps expectedOps = expected.get(i);
+            final HistoricalPackageOps actualOps = actual.get(i);
+            assertHistoricEntriesEqual(expectedOps, actualOps, opNames);
+        }
+    }
+
+    private static void assertHistoricEntriesEqual(AppOpsManager.HistoricalPackageOps expected,
+            AppOpsManager.HistoricalPackageOps actual, String[] opNames) {
+        if (opNames == null) {
+            opNames = AppOpsManager.getOpStrs();
+        }
+        for (String opName : opNames) {
+            assertHistoricEntryEqual(expected, actual, opName);
+        }
+    }
+
+    private static void assertHistoricEntryEqual(HistoricalPackageOps expected,
+            HistoricalPackageOps actual, String opName) {
+        final HistoricalOpEntry expectedEntry = expected.getEntry(opName);
+        final HistoricalOpEntry actualEntry = actual.getEntry(opName);
+        assertEquals(expectedEntry, actualEntry);
+    }
+
+    private static Instrumentation getInstrumentation() {
+        return InstrumentationRegistry.getInstrumentation();
+    }
+
+    private static Context getContext() {
+        return getInstrumentation().getContext();
+    }
 }
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..c45d9ef
--- /dev/null
+++ b/tests/tests/permission/src/android/permission/cts/BackgroundPermissionsTest.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.permission.cts;
+
+import static android.content.pm.PermissionInfo.PROTECTION_DANGEROUS;
+
+import static org.junit.Assert.assertEquals;
+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.Log;
+import android.util.ArrayMap;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+@RunWith(AndroidJUnit4.class)
+public class BackgroundPermissionsTest {
+    private static final String LOG_TAG = BackgroundPermissionsTest.class.getSimpleName();
+
+    @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));
+            }
+        }
+    }
+}
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 d417a9a..5203705 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..1251232
--- /dev/null
+++ b/tests/tests/permission/src/android/permission/cts/LocationAccessCheckTest.java
@@ -0,0 +1,463 @@
+/*
+ * Copyright (C) 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.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.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 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.os.IBinder;
+import android.os.ParcelFileDescriptor;
+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.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;
+
+/**
+ * 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 Context sContext = InstrumentationRegistry.getTargetContext();
+    private static final UiAutomation sUiAutomation = InstrumentationRegistry.getInstrumentation()
+            .getUiAutomation();
+
+    private static final String PERMISSION_CONTROLLER_PKG = sContext.getPackageManager()
+            .getPermissionControllerPackageName();
+
+    /**
+     * 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();
+
+        while (true) {
+            runLocationCheck();
+
+            StatusBarNotification notification;
+            try {
+                notification = eventually(this::getPermissionControllerNotification, 300);
+            } catch (NullPointerException e) {
+                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();
+    }
+
+    /**
+     * 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();
+    }
+
+    @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
index 47055ec..df69c94 100644
--- a/tests/tests/permission/src/android/permission/cts/NoLocationPermissionTest.java
+++ b/tests/tests/permission/src/android/permission/cts/NoLocationPermissionTest.java
@@ -425,39 +425,6 @@
         }
     }
 
-    /**
-     * 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
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/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..5752801
--- /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.READ_CONTACTS);
+        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/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..83c8287 100644
--- a/tests/tests/permission/src/android/permission/cts/ProviderPermissionTest.java
+++ b/tests/tests/permission/src/android/permission/cts/ProviderPermissionTest.java
@@ -23,12 +23,17 @@
 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.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 +42,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 +80,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 +96,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));
     }
 
     /**
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..960ccb0
--- /dev/null
+++ b/tests/tests/permission/src/android/permission/cts/SplitPermissionTest.java
@@ -0,0 +1,603 @@
+/*
+ * Copyright (C) 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR 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 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();
+
+    /**
+     * 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
+     */
+    private 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));
+    }
+
+    /**
+     * 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
+     */
+    private void setAppOp(@NonNull String packageName, @NonNull String permission,
+            int mode){
+        runWithShellPermissionIdentity(() -> sContext.getSystemService(AppOpsManager.class).setMode(
+                        permissionToOpCode(permission),
+                        sContext.getPackageManager().getPackageUid(packageName, 0), packageName,
+                        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
+     */
+    private 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
+     */
+    private void grantPermission(String packageName, 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 (isGranted(packageName, ACCESS_COARSE_LOCATION)) {
+                setAppOp(packageName, ACCESS_COARSE_LOCATION, MODE_ALLOWED);
+            }
+        } else {
+            setAppOp(packageName, permission, MODE_ALLOWED);
+        }
+    }
+
+    /**
+     * 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) {
+        runShellCommand("pm install -r --force-sdk " + apkFile);
+    }
+
+    @After
+    public void uninstallTestApp() {
+        runShellCommand("pm uninstall " + 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
index d117e19..dcc9cdb 100644
--- a/tests/tests/permission/src/android/permission/cts/TelephonyManagerPermissionTest.java
+++ b/tests/tests/permission/src/android/permission/cts/TelephonyManagerPermissionTest.java
@@ -275,48 +275,6 @@
         }
     }
 
-    /**
-     * 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/permission2/AndroidTest.xml b/tests/tests/permission2/AndroidTest.xml
index d4fb85f..91318f8 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" />
     <target_preparer class="com.android.tradefed.targetprep.suite.SuiteApkInstaller">
         <option name="cleanup-apks" value="true" />
         <option name="test-file-name" value="CtsPermission2TestCases.apk" />
diff --git a/tests/tests/permission2/res/raw/android_manifest.xml b/tests/tests/permission2/res/raw/android_manifest.xml
index 040bdbd..fe61b05 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,19 @@
          <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" />
 
     <!-- ====================================================================== -->
     <!-- 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 +815,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 +837,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 +913,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 +923,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 +962,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 +991,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 +1011,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 +1022,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 +1055,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 +1086,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 +1095,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 +1104,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 +1120,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 +1144,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 +1168,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 +1232,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 +1266,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 +1473,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 +1595,20 @@
 
     <!-- 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" />
+
     <!-- #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 +1631,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 +1660,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 +1732,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 +1754,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 +1827,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 -->
@@ -1710,7 +1870,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.
@@ -1837,6 +1997,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
@@ -1932,17 +2099,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.
@@ -1973,6 +2138,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  -->
     <!-- ================================== -->
@@ -2016,6 +2190,13 @@
     <permission android:name="android.permission.INTERACT_ACROSS_USERS_FULL"
         android:protectionLevel="signature|installer" />
 
+    <!-- @SystemApi Allows an application to start an activity within its managed profile from
+         the personal profile.
+         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. -->
@@ -2051,17 +2232,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|privileged" />
 
     <!-- @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.
@@ -2069,6 +2250,21 @@
     <permission android:name="android.permission.START_ANY_ACTIVITY"
         android:protectionLevel="signature" />
 
+    <!-- @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"
@@ -2239,7 +2435,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"
@@ -2284,7 +2481,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" />
@@ -2311,7 +2508,7 @@
     <permission android:name="android.permission.WRITE_GSERVICES"
         android:protectionLevel="signature|privileged" />
 
-    <!-- @SystemApi Allows an application to call
+    <!-- @SystemApi @TestApi Allows an application to call
         {@link android.app.ActivityManager#forceStopPackage}.
         @hide -->
     <permission android:name="android.permission.FORCE_STOP_PACKAGES"
@@ -2346,7 +2543,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
@@ -2416,7 +2613,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" />
@@ -2635,7 +2833,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
@@ -2804,6 +3002,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 -->
@@ -2869,6 +3073,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> -->
@@ -2888,6 +3108,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.
@@ -2949,6 +3176,18 @@
     <permission android:name="android.permission.MANAGE_DEVICE_ADMINS"
         android:protectionLevel="signature|privileged" />
 
+    <!-- @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
          rotation) of the screen.
          <p>Not for use by third-party applications.
@@ -3025,6 +3264,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
@@ -3117,6 +3365,16 @@
     <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
@@ -3124,11 +3382,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 -->
@@ -3166,6 +3426,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
@@ -3217,13 +3484,24 @@
     <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> -->
+    <!-- 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" />
+
+    <!-- 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|privileged" />
 
-    <!-- @SystemApi Allows an application to capture secure video output.
-         <p>Not for use by third-party applications.</p> -->
+    <!-- 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|privileged" />
 
@@ -3266,6 +3544,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"
@@ -3353,7 +3637,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" />
@@ -3469,6 +3753,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. -->
@@ -3579,7 +3867,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"
@@ -3617,11 +3905,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"
@@ -3808,10 +4117,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.
@@ -3907,6 +4216,11 @@
     <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" />
+
     <!-- Allows an app to set the theme overlay in /vendor/overlay
          being used.
          @hide  <p>Not for use by third-party applications.</p> -->
@@ -3969,7 +4283,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.
@@ -3977,6 +4291,58 @@
     <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" />
+
     <application android:process="system"
                  android:persistent="true"
                  android:hasCode="false"
@@ -4132,7 +4498,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>
@@ -4153,26 +4519,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"
@@ -4258,6 +4630,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>
@@ -4282,23 +4662,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>
 
@@ -4306,14 +4673,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/PermissionPolicyTest.java b/tests/tests/permission2/src/android/permission2/cts/PermissionPolicyTest.java
index 2888a71..6916f83 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("\\|");
@@ -267,6 +312,12 @@
                 case "textClassifier": {
                     protectionLevel |= PermissionInfo.PROTECTION_FLAG_SYSTEM_TEXT_CLASSIFIER;
                 } break;
+                case "wellbeing": {
+                    protectionLevel |= PermissionInfo.PROTECTION_FLAG_WELLBEING;
+                } 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 2f155d8..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[] {
@@ -85,6 +85,8 @@
         "android.intent.action.DATA_CONNECTION_FAILED",
         "android.intent.action.NETWORK_SET_TIME",
         "android.intent.action.NETWORK_SET_TIMEZONE",
+        "android.telephony.action.SUBSCRIPTION_CARRIER_IDENTITY_CHANGED",
+        "android.telephony.action.SUBSCRIPTION_PRECISE_CARRIER_IDENTITY_CHANGED",
         "com.android.internal.intent.action.ACTION_SHOW_NOTICE_ECM_BLOCK_OTHERS",
     };
 
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.mk b/tests/tests/provider/Android.mk
index 400f0d2..3deea73 100644
--- a/tests/tests/provider/Android.mk
+++ b/tests/tests/provider/Android.mk
@@ -35,12 +35,8 @@
 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)
@@ -48,4 +44,6 @@
 LOCAL_PACKAGE_NAME := CtsProviderTestCases
 LOCAL_PRIVATE_PLATFORM_APIS := true
 
+LOCAL_MIN_SDK_VERSION := 21
+
 include $(BUILD_CTS_PACKAGE)
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..bca4189 100644
--- a/tests/tests/provider/AndroidTest.xml
+++ b/tests/tests/provider/AndroidTest.xml
@@ -16,6 +16,7 @@
 <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="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/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..1fabb4e 100644
--- a/tests/tests/provider/src/android/provider/cts/BlockedNumberBackupRestoreTest.java
+++ b/tests/tests/provider/src/android/provider/cts/BlockedNumberBackupRestoreTest.java
@@ -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/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/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..8756353 100644
--- a/tests/tests/provider/src/android/provider/cts/MediaStoreAudioTestHelper.java
+++ b/tests/tests/provider/src/android/provider/cts/MediaStoreAudioTestHelper.java
@@ -22,9 +22,13 @@
 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;
+
 /**
  * This class contains fake data and convenient methods for testing:
  * {@link MediaStore.Audio.Media}
@@ -45,6 +49,7 @@
  * @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);
@@ -275,6 +280,11 @@
         }
     }
 
+    @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..0b1dad8 100644
--- a/tests/tests/provider/src/android/provider/cts/MediaStoreIntentsTest.java
+++ b/tests/tests/provider/src/android/provider/cts/MediaStoreIntentsTest.java
@@ -16,11 +16,18 @@
 
 package android.provider.cts;
 
+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.support.test.runner.AndroidJUnit4;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
 
 import java.util.List;
 
@@ -28,45 +35,52 @@
  * Tests to verify that common actions on {@link MediaStore} content are
  * available.
  */
-public class MediaStoreIntentsTest extends AndroidTestCase {
+@RunWith(AndroidJUnit4.class)
+public class MediaStoreIntentsTest {
     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);
         assertCanBeHandled(intent);
     }
 
+    @Test
     public void testPickVideoDir() {
         Intent intent = new Intent(Intent.ACTION_PICK);
         intent.setData(MediaStore.Video.Media.EXTERNAL_CONTENT_URI);
         assertCanBeHandled(intent);
     }
 
+    @Test
     public void testPickAudioDir() {
         Intent intent = new Intent(Intent.ACTION_PICK);
         intent.setData(MediaStore.Audio.Media.EXTERNAL_CONTENT_URI);
         assertCanBeHandled(intent);
     }
 
+    @Test
     public void testViewImageDir() {
         Intent intent = new Intent(Intent.ACTION_VIEW);
         intent.setData(MediaStore.Images.Media.EXTERNAL_CONTENT_URI);
         assertCanBeHandled(intent);
     }
 
+    @Test
     public void testViewVideoDir() {
         Intent intent = new Intent(Intent.ACTION_VIEW);
         intent.setData(MediaStore.Video.Media.EXTERNAL_CONTENT_URI);
         assertCanBeHandled(intent);
     }
 
+    @Test
     public void testViewImageFile() {
         final String[] schemes = new String[] {
                 "file", "http", "https", "content" };
@@ -87,6 +101,7 @@
         }
     }
 
+    @Test
     public void testViewVideoFile() {
         final String[] schemes = new String[] {
                 "file", "http", "https", "content" };
@@ -105,6 +120,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..40ce7a8
--- /dev/null
+++ b/tests/tests/provider/src/android/provider/cts/MediaStorePendingTest.java
@@ -0,0 +1,470 @@
+/*
+ * Copyright (C) 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR 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.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.support.test.runner.AndroidJUnit4;
+
+import libcore.io.IoUtils;
+
+import com.google.common.base.Objects;
+
+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;
+import java.io.FileOutputStream;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.security.DigestInputStream;
+import java.security.MessageDigest;
+import java.util.Arrays;
+import java.util.HashSet;
+import java.util.Set;
+import java.util.regex.Pattern;
+
+@RunWith(AndroidJUnit4.class)
+public class MediaStorePendingTest {
+    private Context mContext;
+    private ContentResolver mResolver;
+
+    private Uri mExternalAudio = MediaStore.Audio.Media.EXTERNAL_CONTENT_URI;
+    private Uri mExternalVideo = MediaStore.Video.Media.EXTERNAL_CONTENT_URI;
+    private Uri mExternalImages = MediaStore.Images.Media.EXTERNAL_CONTENT_URI;
+    private Uri mExternalDownloads = MediaStore.Downloads.EXTERNAL_CONTENT_URI;
+
+    @Before
+    public void setUp() throws Exception {
+        mContext = InstrumentationRegistry.getTargetContext();
+        mResolver = mContext.getContentResolver();
+    }
+
+    @After
+    public void tearDown() throws Exception {
+    }
+
+    @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;
+        final MediaStore.PendingSession session = MediaStore.openPending(mContext, pendingUri);
+        try {
+            try (InputStream in = mContext.getResources().openRawResource(R.raw.scenery);
+                 OutputStream out = session.openOutputStream()) {
+                FileUtils.copy(in, out);
+            }
+            publishUri = session.publish();
+        } finally {
+            IoUtils.closeQuietly(session);
+        }
+
+        // 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;
+
+        final MediaStore.PendingSession session = MediaStore.openPending(mContext, pendingUri);
+        try {
+            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();
+        } finally {
+            IoUtils.closeQuietly(session);
+        }
+
+        // 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);
+        final MediaStore.PendingSession session = MediaStore.openPending(mContext, pendingUri);
+        try {
+            try (InputStream in = mContext.getResources().openRawResource(resId);
+                    OutputStream out = session.openOutputStream()) {
+                FileUtils.copy(in, out);
+            }
+            return session.publish();
+        } finally {
+            IoUtils.closeQuietly(session);
+        }
+    }
+
+    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 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);
+        }
+    }
+
+    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 {
+            IoUtils.closeQuietly(in);
+        }
+    }
+}
diff --git a/tests/tests/provider/src/android/provider/cts/MediaStoreTest.java b/tests/tests/provider/src/android/provider/cts/MediaStoreTest.java
index 7d78bd6..bd188bc 100644
--- a/tests/tests/provider/src/android/provider/cts/MediaStoreTest.java
+++ b/tests/tests/provider/src/android/provider/cts/MediaStoreTest.java
@@ -16,16 +16,50 @@
 
 package android.provider.cts;
 
-import android.content.ContentResolver;
-import android.content.ContentValues;
-import android.database.Cursor;
-import android.net.Uri;
-import android.provider.MediaStore;
-import android.test.InstrumentationTestCase;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNotEquals;
+import static org.junit.Assert.assertNull;
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
 
-public class MediaStoreTest extends InstrumentationTestCase {
+import android.app.usage.StorageStatsManager;
+import android.content.ContentResolver;
+import android.content.ContentUris;
+import android.content.ContentValues;
+import android.content.Context;
+import android.content.res.AssetFileDescriptor;
+import android.database.Cursor;
+import android.media.MediaScanner;
+import android.net.Uri;
+import android.os.Environment;
+import android.os.storage.StorageManager;
+import android.provider.MediaStore;
+import android.provider.MediaStore.MediaColumns;
+import android.support.test.InstrumentationRegistry;
+import android.support.test.runner.AndroidJUnit4;
+
+import libcore.util.HexEncoding;
+
+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.OutputStream;
+import java.util.Arrays;
+import java.util.HashSet;
+import java.util.Set;
+import java.util.UUID;
+
+@RunWith(AndroidJUnit4.class)
+public class MediaStoreTest {
     private static final String TEST_VOLUME_NAME = "volume_for_cts";
 
+    private static final long SIZE_DELTA = 32_000;
+
     private static final String[] PROJECTION = new String[] { MediaStore.MEDIA_SCANNER_VOLUME };
 
     private Uri mScannerUri;
@@ -34,10 +68,15 @@
 
     private ContentResolver mContentResolver;
 
-    @Override
-    protected void setUp() throws Exception {
+    private Context getContext() {
+        return InstrumentationRegistry.getTargetContext();
+    }
+
+    @Before
+    public void setUp() throws Exception {
         mScannerUri = MediaStore.getMediaScannerUri();
-        mContentResolver = getInstrumentation().getContext().getContentResolver();
+        mContentResolver = getContext().getContentResolver();
+
         Cursor c = mContentResolver.query(mScannerUri, PROJECTION, null, null, null);
         if (c != null) {
             c.moveToFirst();
@@ -46,18 +85,20 @@
         }
     }
 
-    @Override
-    protected void tearDown() throws Exception {
+    @After
+    public void tearDown() throws Exception {
+        InstrumentationRegistry.getInstrumentation().getUiAutomation()
+                .dropShellPermissionIdentity();
+
         // restore initial values
         if (mVolumnBackup != null) {
             ContentValues values = new ContentValues();
             values.put(MediaStore.MEDIA_SCANNER_VOLUME, mVolumnBackup);
             mContentResolver.insert(mScannerUri, values);
         }
-        super.tearDown();
     }
 
-
+    @Test
     public void testGetMediaScannerUri() {
         ContentValues values = new ContentValues();
         String selection = MediaStore.MEDIA_SCANNER_VOLUME + "=?";
@@ -85,8 +126,149 @@
         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("internal"));
+        assertTrue(volumeNames.contains("external"));
+    }
+
+    @Test
+    public void testContributedMedia() throws Exception {
+        // STOPSHIP: remove this once isolated storage is always enabled
+        Assume.assumeTrue(StorageManager.hasIsolatedStorage());
+
+        InstrumentationRegistry.getInstrumentation().getUiAutomation().adoptShellPermissionIdentity(
+                android.Manifest.permission.CLEAR_APP_USER_DATA,
+                android.Manifest.permission.PACKAGE_USAGE_STATS);
+
+        // 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(getContext().getExternalMediaDirs()[0],
+                "cts" + System.nanoTime());
+        ProviderTestUtils.stageFile(R.raw.volantis, file);
+        try (MediaScanner scanner = new MediaScanner(getContext(), "external")) {
+            inside = scanner.scanSingleFile(file.getAbsolutePath(), "image/jpeg");
+        }
+        outside = ProviderTestUtils.stageMedia(R.raw.volantis,
+                MediaStore.Images.Media.EXTERNAL_CONTENT_URI);
+
+        {
+            final HashSet<Long> visible = getVisibleIds(
+                    MediaStore.Images.Media.EXTERNAL_CONTENT_URI);
+            assertTrue(visible.contains(ContentUris.parseId(inside)));
+            assertTrue(visible.contains(ContentUris.parseId(outside)));
+
+            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(
+                    MediaStore.Images.Media.EXTERNAL_CONTENT_URI);
+            assertTrue(visible.contains(ContentUris.parseId(inside)));
+            assertFalse(visible.contains(ContentUris.parseId(outside)));
+
+            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(beforeContributed, afterContributed, SIZE_DELTA);
+        }
+    }
+
+    @Test
+    public void testHash() throws Exception {
+        final ContentResolver resolver = getContext().getContentResolver();
+
+        final Uri uri = ProviderTestUtils.stageMedia(R.raw.volantis,
+                MediaStore.Images.Media.EXTERNAL_CONTENT_URI);
+
+        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)) {
+            out.write(42);
+        }
+        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(Environment.getExternalStorageDirectory());
+        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(Environment.getExternalStorageDirectory());
+        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/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..609604f 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,10 +16,15 @@
 
 package android.provider.cts;
 
-import android.provider.cts.R;
+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.content.res.AssetFileDescriptor;
 import android.database.Cursor;
 import android.net.Uri;
@@ -29,23 +34,28 @@
 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.support.test.runner.AndroidJUnit4;
 
-import com.android.compatibility.common.util.FileCopyHelper;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
 
 import java.io.File;
 import java.io.FileNotFoundException;
 
-public class MediaStore_Audio_AlbumsTest extends AndroidTestCase {
+@RunWith(AndroidJUnit4.class)
+public class MediaStore_Audio_AlbumsTest {
+    private Context mContext;
     private ContentResolver mContentResolver;
 
-    @Override
-    protected void setUp() throws Exception {
-        super.setUp();
-
+    @Before
+    public void setUp() throws Exception {
+        mContext = InstrumentationRegistry.getTargetContext();
         mContentResolver = mContext.getContentResolver();
     }
 
+    @Test
     public void testGetContentUri() {
         Cursor c = null;
         assertNotNull(c = mContentResolver.query(
@@ -62,15 +72,17 @@
         assertNull(mContentResolver.query(Albums.getContentUri(volume), null, null, null, null));
     }
 
+    @Test
     public void testStoreAudioAlbumsInternal() {
-        testStoreAudioAlbums(true);
+        doStoreAudioAlbums(true);
     }
 
+    @Test
     public void testStoreAudioAlbumsExternal() {
-        testStoreAudioAlbums(false);
+        doStoreAudioAlbums(false);
     }
 
-    private void testStoreAudioAlbums(boolean isInternal) {
+    private void doStoreAudioAlbums(boolean isInternal) {
         // do not support direct insert operation of the albums
         Uri audioAlbumsUri = isInternal? Albums.INTERNAL_CONTENT_URI : Albums.EXTERNAL_CONTENT_URI;
         try {
@@ -161,15 +173,15 @@
         c.close();
     }
 
+    @Test
     public void testAlbumArt() {
         File path = new File(Environment.getExternalStorageDirectory()
                 + "/test" + System.currentTimeMillis() + ".mp3");
         Uri uri = null;
         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());
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..9f923e5 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,26 +16,39 @@
 
 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.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.support.test.runner.AndroidJUnit4;
 
-public class MediaStore_Audio_ArtistsTest extends InstrumentationTestCase {
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+@RunWith(AndroidJUnit4.class)
+public class MediaStore_Audio_ArtistsTest {
+    private Context mContext;
     private ContentResolver mContentResolver;
 
-    @Override
-    protected void setUp() throws Exception {
-        super.setUp();
-
-        mContentResolver = getInstrumentation().getContext().getContentResolver();
+    @Before
+    public void setUp() throws Exception {
+        mContext = InstrumentationRegistry.getTargetContext();
+        mContentResolver = mContext.getContentResolver();
     }
 
+    @Test
     public void testGetContentUri() {
         Cursor c = null;
         assertNotNull(c = mContentResolver.query(
@@ -52,15 +65,17 @@
         assertNull(mContentResolver.query(Artists.getContentUri(volume), null, null, null, null));
     }
 
+    @Test
     public void testStoreAudioArtistsInternal() {
-        testStoreAudioArtists(true);
+        doStoreAudioArtists(true);
     }
 
+    @Test
     public void testStoreAudioArtistsExternal() {
-        testStoreAudioArtists(false);
+        doStoreAudioArtists(false);
     }
 
-    private void testStoreAudioArtists(boolean isInternal) {
+    private void doStoreAudioArtists(boolean isInternal) {
         Uri artistsUri = isInternal ? Artists.INTERNAL_CONTENT_URI : Artists.EXTERNAL_CONTENT_URI;
         // do not support insert operation of the artists
         try {
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..3b5282d 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,28 +16,40 @@
 
 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.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.support.test.runner.AndroidJUnit4;
 
-public class MediaStore_Audio_Artists_AlbumsTest extends InstrumentationTestCase {
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+@RunWith(AndroidJUnit4.class)
+public class MediaStore_Audio_Artists_AlbumsTest {
+    private Context mContext;
     private ContentResolver mContentResolver;
 
-    @Override
-    protected void setUp() throws Exception {
-        super.setUp();
-
-        mContentResolver = getInstrumentation().getContext().getContentResolver();
+    @Before
+    public void setUp() throws Exception {
+        mContext = InstrumentationRegistry.getTargetContext();
+        mContentResolver = mContext.getContentResolver();
     }
 
+    @Test
     public void testGetContentUri() {
         Cursor c = null;
         Uri contentUri = MediaStore.Audio.Artists.Albums.getContentUri(
@@ -56,15 +68,17 @@
                 null, null, null, null));
     }
 
+    @Test
     public void testStoreAudioArtistsAlbumsInternal() {
-        testStoreAudioArtistsAlbums(true);
+        doStoreAudioArtistsAlbums(true);
     }
 
+    @Test
     public void testStoreAudioArtistsAlbumsExternal() {
-        testStoreAudioArtistsAlbums(false);
+        doStoreAudioArtistsAlbums(false);
     }
 
-    private void testStoreAudioArtistsAlbums(boolean isInternal) {
+    private void doStoreAudioArtistsAlbums(boolean isInternal) {
         // the album item is inserted when inserting audio media
         Uri audioMediaUri = isInternal ? Audio1.getInstance().insertToInternal(mContentResolver)
                 : Audio1.getInstance().insertToExternal(mContentResolver);
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..bc7bd95 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,28 +16,43 @@
 
 package android.provider.cts;
 
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.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.MediaStore.Audio.Media;
 import android.provider.cts.MediaStoreAudioTestHelper.Audio1;
-import android.test.InstrumentationTestCase;
+import android.support.test.InstrumentationRegistry;
+import android.support.test.runner.AndroidJUnit4;
 
-public class MediaStore_Audio_GenresTest extends InstrumentationTestCase {
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+@RunWith(AndroidJUnit4.class)
+public class MediaStore_Audio_GenresTest {
+    private Context mContext;
     private ContentResolver mContentResolver;
 
-    @Override
-    protected void setUp() throws Exception {
-        super.setUp();
-
-        mContentResolver = getInstrumentation().getContext().getContentResolver();
+    @Before
+    public void setUp() throws Exception {
+        mContext = InstrumentationRegistry.getTargetContext();
+        mContentResolver = mContext.getContentResolver();
     }
 
+    @Test
     public void testGetContentUri() {
         Cursor c = null;
         assertNotNull(c = mContentResolver.query(
@@ -59,6 +74,7 @@
         assertNull(mContentResolver.query(Genres.getContentUri(volume), null, null, null, null));
     }
 
+    @Test
     public void testStoreAudioGenresExternal() {
         // insert
         ContentValues values = new ContentValues();
@@ -88,6 +104,7 @@
         }
     }
 
+    @Test
     public void testStoreAudioGenresInternal() {
         // the internal database does not have genres
         ContentValues values = new ContentValues();
@@ -96,6 +113,7 @@
         assertNull(uri);
     }
 
+    @Test
     public void testGetContentUriForAudioId() {
         // Insert an audio file into the content provider.
         ContentValues values = Audio1.getInstance().getContentValues(true);
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..b9f10d8 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,31 +16,42 @@
 
 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.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.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;
 
-public class MediaStore_Audio_Genres_MembersTest extends InstrumentationTestCase {
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+
+public class MediaStore_Audio_Genres_MembersTest {
+    private Context mContext;
     private ContentResolver mContentResolver;
 
     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);
         Cursor c = mContentResolver.query(uri, null, null, null, null);
         c.moveToFirst();
@@ -54,16 +65,16 @@
         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();
     }
 
+    @Test
     public void testGetContentUri() {
         Cursor c = null;
         assertNotNull(c = mContentResolver.query(
@@ -87,6 +98,7 @@
                 null));
     }
 
+    @Test
     public void testStoreAudioGenresMembersExternal() {
         ContentValues values = new ContentValues();
         values.put(Genres.NAME, Audio1.GENRE);
@@ -231,18 +243,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
 
@@ -304,6 +304,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..98fa5fc 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,27 +16,40 @@
 
 package android.provider.cts;
 
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.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.support.test.runner.AndroidJUnit4;
 
-public class MediaStore_Audio_MediaTest extends InstrumentationTestCase {
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+@RunWith(AndroidJUnit4.class)
+public class MediaStore_Audio_MediaTest {
+    private Context mContext;
     private ContentResolver mContentResolver;
 
-    @Override
-    protected void setUp() throws Exception {
-        super.setUp();
-
-        mContentResolver = getInstrumentation().getContext().getContentResolver();
+    @Before
+    public void setUp() throws Exception {
+        mContext = InstrumentationRegistry.getTargetContext();
+        mContentResolver = mContext.getContentResolver();
     }
 
+    @Test
     public void testGetContentUri() {
         Cursor c = null;
         assertNotNull(c = mContentResolver.query(
@@ -53,6 +66,7 @@
         assertNull(mContentResolver.query(Media.getContentUri(volume), null, null, null, null));
     }
 
+    @Test
     public void testGetContentUriForPath() {
         Cursor c = null;
         String externalPath = Environment.getExternalStorageDirectory().getPath();
@@ -60,22 +74,23 @@
                 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();
     }
 
+    @Test
     public void testStoreAudioMediaInternal() {
-        testStoreAudioMedia(true);
+        doStoreAudioMedia(true);
     }
 
+    @Test
     public void testStoreAudioMediaExternal() {
-        testStoreAudioMedia(false);
+        doStoreAudioMedia(false);
     }
 
-    private void testStoreAudioMedia(boolean isInternal) {
+    private void doStoreAudioMedia(boolean isInternal) {
         Audio1 audio1 = Audio1.getInstance();
         ContentValues values = audio1.getContentValues(isInternal);
         //insert
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..a14ba3e 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,28 +16,41 @@
 
 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.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.support.test.runner.AndroidJUnit4;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
 
 import java.util.regex.Pattern;
 
-public class MediaStore_Audio_PlaylistsTest extends InstrumentationTestCase {
+@RunWith(AndroidJUnit4.class)
+public class MediaStore_Audio_PlaylistsTest {
+    private Context mContext;
     private ContentResolver mContentResolver;
 
-    @Override
-    protected void setUp() throws Exception {
-        super.setUp();
-
-        mContentResolver = getInstrumentation().getContext().getContentResolver();
+    @Before
+    public void setUp() throws Exception {
+        mContext = InstrumentationRegistry.getTargetContext();
+        mContentResolver = mContext.getContentResolver();
     }
 
+    @Test
     public void testGetContentUri() {
         Cursor c = null;
         assertNotNull(c = mContentResolver.query(
@@ -61,6 +74,7 @@
                 null));
     }
 
+    @Test
     public void testStoreAudioPlaylistsExternal() {
         final String externalPlaylistPath = Environment.getExternalStorageDirectory().getPath() +
             "/my_favorites.pl";
@@ -109,6 +123,7 @@
         }
     }
 
+    @Test
     public void testStoreAudioPlaylistsInternal() {
         ContentValues values = new ContentValues();
         values.put(Playlists.NAME, "My favourites");
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..2adde5c 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,14 @@
 
 package android.provider.cts;
 
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertTrue;
 
 import android.content.ContentResolver;
 import android.content.ContentValues;
+import android.content.Context;
 import android.database.Cursor;
 import android.net.Uri;
 import android.provider.MediaStore;
@@ -31,11 +36,18 @@
 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.support.test.runner.AndroidJUnit4;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
 
 import java.util.regex.Pattern;
 
-public class MediaStore_Audio_Playlists_MembersTest extends InstrumentationTestCase {
+@RunWith(AndroidJUnit4.class)
+public class MediaStore_Audio_Playlists_MembersTest {
     private String[] mAudioProjection = {
             Members._ID,
             Members.ALBUM,
@@ -93,6 +105,7 @@
             Members.YEAR,
     };
 
+    private Context mContext;
     private ContentResolver mContentResolver;
 
     private long mIdOfAudio1;
@@ -110,11 +123,11 @@
         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();
         mIdOfAudio1 = insertAudioItem(Audio1.getInstance());
         mIdOfAudio2 = insertAudioItem(Audio2.getInstance());
         mIdOfAudio3 = insertAudioItem(Audio3.getInstance());
@@ -122,16 +135,16 @@
         mIdOfAudio5 = insertAudioItem(Audio5.getInstance());
     }
 
-    @Override
-    protected void tearDown() throws Exception {
+    @After
+    public 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();
     }
 
+    @Test
     public void testGetContentUri() {
         assertEquals("content://media/external/audio/playlists/1337/members",
                 Members.getContentUri("external", 1337).toString());
@@ -182,6 +195,7 @@
         c.close();
     }
 
+    @Test
     public void testStoreAudioPlaylistsMembersExternal() {
         ContentValues values = new ContentValues();
         values.put(Playlists.NAME, "My favourites");
@@ -451,6 +465,7 @@
         }
     }
 
+    @Test
     public void testStoreAudioPlaylistsMembersInternal() {
         ContentValues values = new ContentValues();
         values.put(Playlists.NAME, "My favourites");
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..63e62c7
--- /dev/null
+++ b/tests/tests/provider/src/android/provider/cts/MediaStore_DownloadsTest.java
@@ -0,0 +1,350 @@
+/*
+ * Copyright (C) 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR 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.assertArrayEquals;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertNull;
+
+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.Images;
+import android.support.test.InstrumentationRegistry;
+import android.support.test.runner.AndroidJUnit4;
+import android.util.Log;
+
+import libcore.io.IoUtils;
+
+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.security.DigestInputStream;
+import java.security.MessageDigest;
+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();
+
+        // can not accept any other volume names
+        final String volume = "faveVolume";
+        assertNull(mContentResolver.query(Downloads.getContentUri(volume),
+                null, null, null, null));
+    }
+
+    @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;
+        final MediaStore.PendingSession session = MediaStore.openPending(mContext, pendingUri);
+        try {
+            try (InputStream in = mContext.getResources().openRawResource(R.raw.testvideo);
+                 OutputStream out = session.openOutputStream()) {
+                android.os.FileUtils.copy(in, out);
+            }
+            publishUri = session.publish();
+        } finally {
+            IoUtils.closeQuietly(session);
+        }
+
+        final String newDisplayName = "cts" + System.nanoTime();
+        final ContentValues updateValues = new ContentValues();
+        updateValues.put(Downloads.DISPLAY_NAME, newDisplayName);
+        assertEquals(1, mContentResolver.update(publishUri, updateValues, null, null));
+
+        try (Cursor cursor = mContentResolver.query(Downloads.EXTERNAL_CONTENT_URI,
+                null, "_display_name LIKE ?1", new String[] { newDisplayName }, null)) {
+            assertEquals(1, cursor.getCount());
+            cursor.moveToNext();
+            assertEquals("video/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;
+        final MediaStore.PendingSession session = MediaStore.openPending(mContext, pendingUri);
+        try {
+            try (InputStream in = mContext.getResources().openRawResource(R.raw.testvideo);
+                 OutputStream out = session.openOutputStream()) {
+                android.os.FileUtils.copy(in, out);
+            }
+            publishUri = session.publish();
+        } finally {
+            IoUtils.closeQuietly(session);
+        }
+
+        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;
+        final MediaStore.PendingSession session = MediaStore.openPending(mContext, pendingUri);
+        try {
+            try (InputStream in = mContext.getResources().openRawResource(R.raw.testvideo);
+                 OutputStream out = session.openOutputStream()) {
+                android.os.FileUtils.copy(in, out);
+            }
+            publishUri = session.publish();
+        } finally {
+            IoUtils.closeQuietly(session);
+        }
+        mCountDownLatch.await(NOTIFY_TIMEOUT_MILLIS, TimeUnit.MILLISECONDS);
+
+        mCountDownLatch = new CountDownLatch(1);
+        final String newDisplayName = "cts" + System.nanoTime();
+        final ContentValues updateValues = new ContentValues();
+        updateValues.put(Downloads.DISPLAY_NAME, newDisplayName);
+        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();
+                });
+
+        latch.await(SCAN_TIMEOUT_MILLIS, TimeUnit.MILLISECONDS);
+        assertNotNull("Failed to scan " + file.getAbsolutePath(), mediaStoreUris[0]);
+        return mediaStoreUris[0];
+    }
+
+    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 {
+            IoUtils.closeQuietly(in);
+        }
+    }
+}
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..380ba82 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.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;
@@ -26,11 +36,15 @@
 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.support.test.runner.AndroidJUnit4;
 
-import com.android.compatibility.common.util.FileCopyHelper;
+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;
@@ -41,20 +55,21 @@
 import java.util.Collections;
 import java.util.List;
 
-public class MediaStore_FilesTest extends AndroidTestCase {
-
+@RunWith(AndroidJUnit4.class)
+public class MediaStore_FilesTest {
+    private Context mContext;
     private ContentResolver mResolver;
 
-    @Override
-    protected void setUp() throws Exception {
-        super.setUp();
+    @Before
+    public void setUp() throws Exception {
+        mContext = InstrumentationRegistry.getTargetContext();
         mResolver = mContext.getContentResolver();
+
         cleanup();
     }
 
-    @Override
-    protected void tearDown() throws Exception {
-        super.tearDown();
+    @After
+    public void tearDown() throws Exception {
         cleanup();
     }
 
@@ -89,6 +104,7 @@
         f.delete();
     }
 
+    @Test
     public void testGetContentUri() {
         String volumeName = MediaStoreAudioTestHelper.EXTERNAL_VOLUME_NAME;
         Uri allFilesUri = MediaStore.Files.getContentUri(volumeName);
@@ -97,14 +113,7 @@
         // 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.
@@ -176,6 +185,7 @@
         }
     }
 
+    @Test
     public void testCaseSensitivity() throws IOException {
         String fileDir = Environment.getExternalStorageDirectory() +
                 "/" + getClass().getCanonicalName();
@@ -206,7 +216,8 @@
         }
     }
 
-    public void testAccess() throws IOException {
+    @Test
+    public void testAccess() throws Exception {
         // clean up from previous run
         mResolver.delete(MediaStore.Images.Media.INTERNAL_CONTENT_URI,
                 "_data NOT LIKE ?", new String[] { "/system/%" } );
@@ -289,7 +300,7 @@
                     "/" + getClass().getCanonicalName() + "/test.mp3";
             sdfile = new File(fileDir);
             writeFile(R.raw.testmp3, sdfile.getCanonicalPath());
-            assertTrue(sdfile.exists());
+            assertExists(sdfile);
             values = new ContentValues();
             values.put("_data", sdfile.getCanonicalPath());
             mResolver.update(uri, values, null, null);
@@ -391,8 +402,8 @@
 
         for (String s: trimmedPaths) {
             File dir = new File(s + "/foobardir-" + SystemClock.elapsedRealtime());
-            assertFalse("please remove " + dir.getAbsolutePath()
-                    + " before running", dir.exists());
+            assertNotExists("please remove " + dir.getAbsolutePath()
+                    + " before running", dir.getAbsolutePath());
             File file = new File(dir, "foobar");
             values = new ContentValues();
             values.put(MediaStore.Audio.Media.ALBUM_ID, albumid);
@@ -406,7 +417,7 @@
             } finally {
                 pfd.close();
             }
-            assertFalse(dir.getAbsolutePath() + " was created", dir.exists());
+            assertNotExists(dir.getAbsolutePath() + " was created", dir.getAbsolutePath());
         }
         mResolver.delete(fileUri, null, null);
         new File(fileName).delete();
@@ -422,7 +433,7 @@
             assertNotNull(fileUri);
 
             // check that adding the file doesn't cause it to be created
-            assertFalse(file.exists());
+            assertNotExists(file);
 
             // check if opening the file for write works
             try {
@@ -433,7 +444,7 @@
             }
             // check that deleting the file doesn't cause it to be created
             mResolver.delete(fileUri, null, null);
-            assertFalse(file.exists());
+            assertNotExists(file);
         }
 
         // try creating files in new subdir
@@ -453,8 +464,8 @@
             assertNotNull(fileUri);
 
             // check that adding the file or its folder didn't cause either one to be created
-            assertFalse(dir.exists());
-            assertFalse(file.exists());
+            assertNotExists(dir);
+            assertNotExists(file);
 
             // check if opening the file for write works
             try {
@@ -465,11 +476,11 @@
             }
             // 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());
+            assertNotExists(dir);
+            assertNotExists(file);
             mResolver.delete(dirUri, null, null);
-            assertFalse(dir.exists());
-            assertFalse(file.exists());
+            assertNotExists(dir);
+            assertNotExists(file);
         }
     }
 
@@ -483,6 +494,7 @@
         return paths;
     }
 
+    @Test
     public void testUpdateMediaType() throws Exception {
         String fileDir = Environment.getExternalStorageDirectory() +
                 "/" + getClass().getCanonicalName();
@@ -523,8 +535,7 @@
         File out = new File(path);
         File dir = out.getParentFile();
         dir.mkdirs();
-        FileCopyHelper copier = new FileCopyHelper(mContext);
-        copier.copyToExternalStorage(resid, out);
+        ProviderTestUtils.stageFile(resid, out);
     }
 
     private int getFileCount(Uri uri) {
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..fe9010d 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,11 @@
 
 package android.provider.cts;
 
-import android.provider.cts.R;
+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;
@@ -24,23 +28,38 @@
 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.support.test.runner.AndroidJUnit4;
 import android.util.Log;
 
-import com.android.compatibility.common.util.FileCopyHelper;
 import com.android.compatibility.common.util.FileUtils;
 
+import libcore.io.IoUtils;
+
+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.lang.Math;
+import java.io.InputStream;
+import java.io.OutputStream;
 import java.util.ArrayList;
 
-public class MediaStore_Images_MediaTest extends InstrumentationTestCase {
+@RunWith(AndroidJUnit4.class)
+public class MediaStore_Images_MediaTest {
     private static final String MIME_TYPE_JPEG = "image/jpeg";
 
     private static final String TEST_TITLE1 = "test title1";
@@ -63,26 +82,18 @@
 
     private ContentResolver mContentResolver;
 
-    private FileCopyHelper mHelper;
-
-    @Override
-    protected void tearDown() throws Exception {
+    @After
+    public void tearDown() throws Exception {
         for (Uri row : mRowsAdded) {
             mContentResolver.delete(row, null, null);
         }
-
-        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>();
 
         File pics = Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_PICTURES);
@@ -96,6 +107,7 @@
 
     }
 
+    @Test
     public void testInsertImageWithImagePath() throws Exception {
         Cursor c = Media.query(mContentResolver, Media.EXTERNAL_CONTENT_URI, null, null,
                 "_id ASC");
@@ -103,7 +115,9 @@
         c.close();
 
         // insert an image by path
-        String path = mHelper.copy(R.raw.scenery, "mediaStoreTest1.jpg");
+        File file = mContext.getFileStreamPath("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);
@@ -117,7 +131,9 @@
         mRowsAdded.add(Uri.parse(stringUrl));
 
         // insert another image by path
-        path = mHelper.copy(R.raw.scenery, "mediaStoreTest2.jpg");
+        file = mContext.getFileStreamPath("mediaStoreTest2.jpg");
+        path = file.getAbsolutePath();
+        ProviderTestUtils.stageFile(R.raw.scenery, file);
         stringUrl = null;
         try {
             stringUrl = Media.insertImage(mContentResolver, path, TEST_TITLE2, TEST_DESCRIPTION2);
@@ -167,6 +183,7 @@
         c.close();
     }
 
+    @Test
     public void testInsertImageWithBitmap() throws Exception {
         // insert the image by bitmap
         Bitmap src = BitmapFactory.decodeResource(mContext.getResources(), R.raw.scenery);
@@ -194,6 +211,7 @@
     }
 
     @Presubmit
+    @Test
     public void testGetContentUri() {
         Cursor c = null;
         assertNotNull(c = mContentResolver.query(Media.getContentUri("internal"), null, null, null,
@@ -213,6 +231,7 @@
         new File(path).delete();
     }
 
+    @Test
     public void testStoreImagesMediaExternal() throws Exception {
         final String externalPath = Environment.getExternalStorageDirectory().getPath() +
                 "/testimage.jpg";
@@ -232,8 +251,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);
@@ -262,12 +279,10 @@
             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)));
-            assertEquals("testimage", c.getString(c.getColumnIndex(Media.DISPLAY_NAME)));
+            assertEquals("testimage.jpg", c.getString(c.getColumnIndex(Media.DISPLAY_NAME)));
             assertEquals("image/jpeg", c.getString(c.getColumnIndex(Media.MIME_TYPE)));
             assertEquals("testimage", c.getString(c.getColumnIndex(Media.TITLE)));
             assertEquals(numBytes, c.getInt(c.getColumnIndex(Media.SIZE)));
@@ -285,8 +300,6 @@
             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);
@@ -307,8 +320,6 @@
             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,
@@ -327,6 +338,7 @@
         }
     }
 
+    @Test
     public void testStoreImagesMediaInternal() {
         // can not insert any data, so other operations can not be tested
         try {
@@ -354,4 +366,105 @@
         assertEquals(2, c.getCount());
         c.close();
     }
+
+    /**
+     * 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(
+                MediaStore.Images.Media.EXTERNAL_CONTENT_URI, displayName, "image/jpeg");
+
+        final Uri pendingUri = MediaStore.createPending(mContext, params);
+        final Uri publishUri;
+        final MediaStore.PendingSession session = MediaStore.openPending(mContext, pendingUri);
+        try {
+            try (InputStream in = mContext.getResources().openRawResource(R.raw.volantis);
+                 OutputStream out = session.openOutputStream()) {
+                android.os.FileUtils.copy(in, out);
+            }
+            publishUri = session.publish();
+        } finally {
+            IoUtils.closeQuietly(session);
+        }
+
+        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(
+                MediaStore.Images.Media.EXTERNAL_CONTENT_URI, displayName, "image/jpeg");
+
+        final Uri pendingUri = MediaStore.createPending(mContext, params);
+        final Uri publishUri;
+        final MediaStore.PendingSession session = MediaStore.openPending(mContext, pendingUri);
+        try {
+            try (InputStream in = mContext.getResources().openRawResource(R.raw.volantis);
+                    OutputStream out = session.openOutputStream()) {
+                android.os.FileUtils.copy(in, out);
+            }
+            publishUri = session.publish();
+        } finally {
+            IoUtils.closeQuietly(session);
+        }
+
+        // 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..ee6fbe5 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,7 +16,15 @@
 
 package android.provider.cts;
 
-import android.provider.cts.R;
+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;
@@ -25,31 +33,40 @@
 import android.database.Cursor;
 import android.graphics.Bitmap;
 import android.graphics.BitmapFactory;
+import android.media.MediaScanner;
 import android.net.Uri;
 import android.os.Environment;
 import android.provider.MediaStore;
 import android.provider.MediaStore.Images.Media;
 import android.provider.MediaStore.Images.Thumbnails;
 import android.provider.MediaStore.MediaColumns;
-import android.test.InstrumentationTestCase;
+import android.support.test.InstrumentationRegistry;
+import android.support.test.runner.AndroidJUnit4;
+import android.util.DisplayMetrics;
 
-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 java.io.File;
 import java.util.ArrayList;
-import android.util.DisplayMetrics;
 
-public class MediaStore_Images_ThumbnailsTest extends InstrumentationTestCase {
+@RunWith(AndroidJUnit4.class)
+public class MediaStore_Images_ThumbnailsTest {
     private ArrayList<Uri> mRowsAdded;
 
     private Context mContext;
 
     private ContentResolver mContentResolver;
 
-    private FileCopyHelper mHelper;
+    private Uri mRed;
+    private Uri mBlue;
 
-    @Override
-    protected void tearDown() throws Exception {
+    @After
+    public void tearDown() throws Exception {
         for (Uri row : mRowsAdded) {
             try {
                 mContentResolver.delete(row, null, null);
@@ -58,34 +75,53 @@
                 // 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>();
     }
 
-    public void testQueryInternalThumbnails() throws Exception {
+    private void prepareImages() throws Exception {
+        final File red = new File(Environment.getExternalStorageDirectory(), "red.jpg");
+        final File blue = new File(Environment.getExternalStorageDirectory(), "blue.jpg");
+        ProviderTestUtils.stageFile(R.raw.scenery, red);
+        ProviderTestUtils.stageFile(R.raw.scenery, blue);
+        try (MediaScanner scanner = new MediaScanner(mContext, "external")) {
+            mRed = scanner.scanSingleFile(red.getAbsolutePath(), "image/jpeg");
+            mBlue = scanner.scanSingleFile(blue.getAbsolutePath(), "image/jpeg");
+        }
+        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 {
+        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 = mContext.getFileStreamPath("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 +134,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 +148,8 @@
         c.close();
     }
 
-    public void testQueryExternalMiniThumbnails() {
+    @Test
+    public void testQueryExternalMiniThumbnails() throws Exception {
         // insert the image by bitmap
         BitmapFactory.Options opts = new BitmapFactory.Options();
         opts.inTargetDensity = DisplayMetrics.DENSITY_XHIGH;
@@ -136,22 +173,22 @@
         String 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);
 
         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));
+        assertMostlyEquals(320, c.getInt(c.getColumnIndex(Thumbnails.WIDTH)), 128);
+        assertMostlyEquals(320, c.getInt(c.getColumnIndex(Thumbnails.HEIGHT)), 128);
         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)));
+        assertMostlyEquals(96, c.getInt(c.getColumnIndex(Thumbnails.WIDTH)), 64);
+        assertMostlyEquals(96, c.getInt(c.getColumnIndex(Thumbnails.HEIGHT)), 64);
         c.close();
 
         c = Thumbnails.queryMiniThumbnail(mContentResolver, imageId, Thumbnails.MINI_KIND,
@@ -161,7 +198,7 @@
         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("thumbnail file does not exist", thumbPath);
 
         // 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,14 +206,14 @@
         mContentResolver.delete(stringUri, null, null);
         mRowsAdded.remove(stringUri);
 
-        assertFalse("image file should no longer exist", new File(imagePath).exists());
+        assertNotExists("image file should no longer exist", imagePath);
 
         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());
+        assertNotExists("thumbnail file should no longer exist", thumbPath);
         c.close();
 
         // insert image, then delete it via the files table
@@ -187,10 +224,10 @@
         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());
+        assertNotExists("image file should no longer exist", imagePath);
 
 
         // insert image, then delete its thumbnail
@@ -205,7 +242,7 @@
                 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());
+        assertExists("thumbnail file does not exist", thumbPath);
 
         Uri imguri = Uri.parse(stringUrl);
         long imgid = ContentUris.parseId(imguri);
@@ -213,10 +250,11 @@
         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("thumbnail file should no longer exist", thumbPath);
+        assertExists("image file should still exist", imagePath);
     }
 
+    @Test
     public void testGetContentUri() {
         Cursor c = null;
         assertNotNull(c = mContentResolver.query(Thumbnails.getContentUri("internal"), null, null,
@@ -232,14 +270,17 @@
                 null));
     }
 
-    public void testStoreImagesMediaExternal() {
+    @Test
+    public void testStoreImagesMediaExternal() throws Exception {
+        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 +296,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 +305,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,6 +315,7 @@
         assertEquals(1, mContentResolver.delete(uri, null, null));
     }
 
+    @Test
     public void testThumbnailGenerationAndCleanup() throws Exception {
         // insert an image
         Bitmap src = BitmapFactory.decodeResource(mContext.getResources(), R.raw.scenery);
@@ -290,11 +332,11 @@
         assertTrue("couldn't find thumbnail", c.moveToNext());
         String path = c.getString(0);
         c.close();
-        assertTrue("thumbnail does not exist", new File(path).exists());
+        assertExists("thumbnail does not exist", path);
 
         // 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());
+        assertNotExists("thumbnail still exists after source file delete", path);
 
         // insert again
         uri = Uri.parse(Media.insertImage(mContentResolver, src, "test", "test description"));
@@ -310,7 +352,7 @@
         assertTrue("couldn't find thumbnail", c.moveToNext());
         path = c.getString(0);
         c.close();
-        assertTrue("thumbnail does not exist", new File(path).exists());
+        assertExists("thumbnail does not exist", path);
 
         // update the media type
         ContentValues values = new ContentValues();
@@ -332,7 +374,7 @@
             assertFalse("thumbnail entry exists for non-thumbnail file", c.moveToNext());
             c.close();
         }
-        assertFalse("thumbnail remains after source file type change", new File(path).exists());
+        assertNotExists("thumbnail remains after source file type change", path);
 
         // check source no longer exists as image
         c = mContentResolver.query(uri,
@@ -351,10 +393,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,6 +409,7 @@
         }
     }
 
+    @Test
     public void testThumbnailOrderedQuery() throws Exception {
         Bitmap src = BitmapFactory.decodeResource(mContext.getResources(), R.raw.scenery);
         Uri url[] = new Uri[3];
@@ -378,35 +422,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 +435,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);
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..1a82b21 100644
--- a/tests/tests/provider/src/android/provider/cts/MediaStore_VideoTest.java
+++ b/tests/tests/provider/src/android/provider/cts/MediaStore_VideoTest.java
@@ -16,7 +16,7 @@
 
 package android.provider.cts;
 
-import android.provider.cts.R;
+import static org.junit.Assert.assertEquals;
 
 import android.content.ContentResolver;
 import android.content.ContentValues;
@@ -25,13 +25,19 @@
 import android.net.Uri;
 import android.provider.MediaStore.Video;
 import android.provider.MediaStore.Video.VideoColumns;
-import android.test.InstrumentationTestCase;
+import android.support.test.InstrumentationRegistry;
+import android.support.test.runner.AndroidJUnit4;
 
-import com.android.compatibility.common.util.FileCopyHelper;
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
 
+import java.io.File;
 import java.util.ArrayList;
 
-public class MediaStore_VideoTest extends InstrumentationTestCase {
+@RunWith(AndroidJUnit4.class)
+public class MediaStore_VideoTest {
     private static final String TEST_VIDEO_3GP = "testVideo.3gp";
 
     private ArrayList<Uri> mRowsAdded;
@@ -40,29 +46,28 @@
 
     private ContentResolver mContentResolver;
 
-    private FileCopyHelper mHelper;
-
-    @Override
-    protected void tearDown() throws Exception {
+    @After
+    public void tearDown() throws Exception {
         for (Uri row : mRowsAdded) {
             mContentResolver.delete(row, null, null);
         }
-        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>();
     }
 
+    @Test
     public void testQuery() throws Exception {
         ContentValues values = new ContentValues();
-        String valueOfData = mHelper.copy(R.raw.testvideo, TEST_VIDEO_3GP);
+
+        final File file = mContext.getFileStreamPath(TEST_VIDEO_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);
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..8d3ba29 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,8 +16,14 @@
 
 package android.provider.cts;
 
+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.assertNull;
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
 
 import android.content.ContentResolver;
 import android.content.ContentValues;
@@ -28,24 +34,30 @@
 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.support.test.runner.AndroidJUnit4;
 
-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 java.io.File;
 import java.io.IOException;
 
-public class MediaStore_Video_MediaTest extends AndroidTestCase {
+@RunWith(AndroidJUnit4.class)
+public class MediaStore_Video_MediaTest {
+    private Context mContext;
     private ContentResolver mContentResolver;
 
-    @Override
-    protected void setUp() throws Exception {
-        super.setUp();
-
-        mContentResolver = getContext().getContentResolver();
+    @Before
+    public void setUp() throws Exception {
+        mContext = InstrumentationRegistry.getTargetContext();
+        mContentResolver = mContext.getContentResolver();
     }
 
+    @Test
     public void testGetContentUri() {
         Cursor c = null;
         assertNotNull(c = mContentResolver.query(Media.getContentUri("internal"), null, null, null,
@@ -65,6 +77,7 @@
         new File(path).delete();
     }
 
+    @Test
     public void testStoreVideoMediaExternal() throws Exception {
         final String externalVideoPath = Environment.getExternalStorageDirectory().getPath() +
                  "/video/testvideo.3gp";
@@ -88,8 +101,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");
@@ -123,8 +134,6 @@
             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)));
@@ -149,8 +158,6 @@
             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");
@@ -176,8 +183,6 @@
             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)));
@@ -198,24 +203,25 @@
         }
 
         // 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());
+        assertExists(videofile);
         mContentResolver.delete(videoUri, null, null);
-        assertFalse(videofile.exists());
+        assertNotExists(videofile);
 
         // insert again, then delete with the "delete data" parameter set to false
         videoUri = insertVideo(context);
-        assertTrue(videofile.exists());
+        assertExists(videofile);
         Uri.Builder builder = videoUri.buildUpon();
         builder.appendQueryParameter(MediaStore.PARAM_DELETE_DATA, "false");
         mContentResolver.delete(builder.build(), null, null);
-        assertTrue(videofile.exists());
+        assertExists(videofile);
         videofile.delete();
 
     }
 
+    @Test
     public void testStoreVideoMediaInternal() {
         // can not insert any data, so other operations can not be tested
         try {
@@ -232,7 +238,7 @@
         // 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());
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..677b5fb 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,59 @@
 
 package android.provider.cts;
 
-import android.provider.cts.R;
+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.database.Cursor;
-import android.media.MediaCodecInfo;
-import android.media.MediaCodecList;
 import android.net.Uri;
 import android.os.Environment;
 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.support.test.runner.AndroidJUnit4;
 import android.util.Log;
 
-import com.android.compatibility.common.util.FileCopyHelper;
 import com.android.compatibility.common.util.MediaUtils;
 
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
 import java.io.File;
 import java.io.IOException;
 
-public class MediaStore_Video_ThumbnailsTest extends AndroidTestCase {
+@RunWith(AndroidJUnit4.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();
+    @Before
+    public void setUp() throws Exception {
+        mContext = InstrumentationRegistry.getTargetContext();
         mResolver = mContext.getContentResolver();
-        mFileHelper = new FileCopyHelper(mContext);
     }
 
-    @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,6 +76,7 @@
         assertEquals(Thumbnails.EXTERNAL_CONTENT_URI, externalUri);
     }
 
+    @Test
     public void testGetThumbnail() throws Exception {
         // Insert a video into the provider.
         Uri videoUri = insertVideo();
@@ -113,17 +119,18 @@
             long vid = c.getLong(2);
             assertEquals(videoId, vid);
             String path = c.getString(1);
-            assertTrue("thumbnail file does not exist", new File(path).exists());
+            assertExists("thumbnail file does not exist", path);
             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());
+            assertNotExists("thumbnail file should no longer exist", path);
         }
         c.close();
 
         assertEquals(1, mResolver.delete(videoUri, null, null));
     }
 
+    @Test
     public void testThumbnailGenerationAndCleanup() throws Exception {
 
         if (!hasCodec()) {
@@ -150,11 +157,11 @@
         assertTrue("couldn't find thumbnail", c.moveToNext());
         String path = c.getString(0);
         c.close();
-        assertTrue("thumbnail does not exist", new File(path).exists());
+        assertExists("thumbnail does not exist", path);
 
         // 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());
+        assertNotExists("thumbnail still exists after source file delete", path);
 
         // insert again
         uri = insertVideo();
@@ -174,7 +181,7 @@
         assertTrue("couldn't find thumbnail", c.moveToNext());
         path = c.getString(0);
         c.close();
-        assertTrue("thumbnail does not exist", new File(path).exists());
+        assertExists("thumbnail does not exist", path);
 
         // update the media type
         ContentValues values = new ContentValues();
@@ -196,7 +203,7 @@
             assertFalse("thumbnail entry exists for non-thumbnail file", c.moveToNext());
             c.close();
         }
-        assertFalse("thumbnail remains after source file type change", new File(path).exists());
+        assertNotExists("thumbnail remains after source file type change", path);
 
         // check source no longer exists as video
         c = mResolver.query(uri,
@@ -215,7 +222,7 @@
         c.close();
 
         // clean up
-        mResolver.delete(uri, null /* where */, null /* where args */);
+        mResolver.delete(fileUri, null /* where */, null /* where args */);
         new File(sourcePath).delete();
     }
 
@@ -225,7 +232,8 @@
         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());
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..d3e0af1 100644
--- a/tests/tests/provider/src/android/provider/cts/ProviderTestUtils.java
+++ b/tests/tests/provider/src/android/provider/cts/ProviderTestUtils.java
@@ -16,14 +16,26 @@
 
 package android.provider.cts;
 
+import static org.junit.Assert.fail;
+
 import android.app.UiAutomation;
+import android.content.Context;
+import android.net.Uri;
 import android.os.ParcelFileDescriptor;
+import android.provider.MediaStore;
+import android.support.test.InstrumentationRegistry;
+import android.system.ErrnoException;
+import android.system.Os;
+import android.system.OsConstants;
 
 import java.io.BufferedReader;
+import java.io.File;
 import java.io.FileInputStream;
+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.util.Objects;
 import java.util.regex.Matcher;
@@ -40,9 +52,10 @@
 
     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, UiAutomation uiAutomation)
@@ -119,4 +132,77 @@
             throws Exception {
         executeShellCommand("bmgr wipe " + backupTransport + " " + packageName, uiAutomation);
     }
+
+    static String stageInternalFile(int resId, String fileName) throws IOException {
+        final Context context = InstrumentationRegistry.getTargetContext();
+        try (InputStream source = context.getResources().openRawResource(resId);
+                OutputStream target = context.openFileOutput(fileName, Context.MODE_PRIVATE)) {
+            android.os.FileUtils.copy(source, target);
+        }
+        return context.getFileStreamPath(fileName).getAbsolutePath();
+    }
+
+    static void 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);
+        }
+    }
+
+    static Uri stageMedia(int resId, 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 (InputStream source = context.getResources().openRawResource(resId);
+                    OutputStream target = session.openOutputStream()) {
+                android.os.FileUtils.copy(source, target);
+            }
+            return session.publish();
+        }
+    }
+
+    public static void assertExists(String path) throws ErrnoException {
+        assertExists(null, path);
+    }
+
+    public static void assertExists(File file) throws ErrnoException {
+        assertExists(null, file.getAbsolutePath());
+    }
+
+    public static void assertExists(String msg, String path) throws ErrnoException {
+        try {
+            Os.access(path, OsConstants.F_OK);
+        } catch (ErrnoException e) {
+            if (e.errno == OsConstants.ENOENT) {
+                fail(msg);
+            } else {
+                throw e;
+            }
+        }
+    }
+
+    public static void assertNotExists(String path) throws ErrnoException {
+        assertNotExists(null, path);
+    }
+
+    public static void assertNotExists(File file) throws ErrnoException {
+        assertNotExists(null, file.getAbsolutePath());
+    }
+
+    public static void assertNotExists(String msg, String path) throws ErrnoException {
+        try {
+            Os.access(path, OsConstants.F_OK);
+            fail(msg);
+        } catch (ErrnoException e) {
+            if (e.errno == OsConstants.ENOENT) {
+                return;
+            } else {
+                throw e;
+            }
+        }
+    }
 }
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/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_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/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/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..a086b26
--- /dev/null
+++ b/tests/tests/role/CtsRoleTestApp/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.app.role.cts.app">
+
+    <application>
+
+        <activity
+            android:name=".RequestRoleActivity"
+            android:exported="true" />
+
+        <activity android:name=".EmptyActivity">
+
+            <!-- Dialer -->
+            <intent-filter>
+                <action android:name="android.intent.action.DIAL" />
+            </intent-filter>
+            <intent-filter>
+                <action android:name="android.intent.action.DIAL" />
+                <data android:scheme="tel" />
+            </intent-filter>
+        </activity>
+    </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..73f2009
--- /dev/null
+++ b/tests/tests/role/src/android/app/role/cts/RoleManagerTest.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.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.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.Until;
+import android.util.Pair;
+
+import androidx.annotation.NonNull;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.io.IOException;
+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 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);
+    }
+
+    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";
+        sUiDevice.wait(Until.findObject(By.res(buttonId)), TIMEOUT_MILLIS).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 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/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..886db6e 100644
--- a/tests/tests/security/src/android/security/cts/ActivityManagerTest.java
+++ b/tests/tests/security/src/android/security/cts/ActivityManagerTest.java
@@ -75,6 +75,7 @@
         }
     }
 
+    @SecurityTest(minPatchLevel = "2019-01")
     public void testIsAppInForegroundNormal() throws Exception {
         /* Verify that isAppForeground can be called by the caller on itself. */
         launchActivity(NormalActivity.class);
@@ -85,6 +86,7 @@
         assertTrue("isAppForeground failed to query for uid on itself.", sIsAppForeground);
     }
 
+    @SecurityTest(minPatchLevel = "2019-01")
     public void testIsAppInForegroundMalicious() throws Exception {
         /* Verify that isAppForeground cannot be called by another app on a known uid. */
         launchActivity(MaliciousActivity.class);
@@ -187,4 +189,4 @@
             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/ListeningPortsTest.java b/tests/tests/security/src/android/security/cts/ListeningPortsTest.java
deleted file mode 100644
index 0e3c72b5..0000000
--- a/tests/tests/security/src/android/security/cts/ListeningPortsTest.java
+++ /dev/null
@@ -1,397 +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
-        //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
-    }
-
-    @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 4a8cc780..d36e420 100644
--- a/tests/tests/security/src/android/security/cts/MotionEventTest.java
+++ b/tests/tests/security/src/android/security/cts/MotionEventTest.java
@@ -39,6 +39,7 @@
 import android.view.WindowManager;
 
 import com.android.compatibility.common.util.PollingCheck;
+import com.android.compatibility.common.util.WidgetTestUtils;
 
 import org.junit.Before;
 import org.junit.Rule;
@@ -80,13 +81,12 @@
      * screen to determine approximate locations of touch events without the user knowing.
      */
     @Test
-    public void testActionOutsideDoesNotContainedObscuredInformation() throws Exception {
+    public void testActionOutsideDoesNotContainedObscuredInformation() throws Throwable {
         enableAppOps();
         final OnTouchListener listener = new OnTouchListener();
-        final Point size = new Point();
-        final View[] viewHolder = new View[1];
-        mActivity.runOnUiThread(() -> {
+        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(
@@ -115,20 +115,26 @@
             v.setBackgroundColor(Color.BLUE);
             v.setOnTouchListener(listener);
             v.setLayoutParams(vglp);
-            viewHolder[0] = v;
 
             wm.addView(v, wmlp);
+            return v;
         });
-        mInstrumentation.waitForIdleSync();
+        mActivity.runOnUiThread(addViewTask);
+        View view = addViewTask.get(5, TimeUnit.SECONDS);
 
-        FutureTask<Point> task = new FutureTask<>(() -> {
+        // Wait for a layout pass to be certain the view is on the screen
+        // before getting the location and injecting touches.
+        WidgetTestUtils.runOnMainAndLayoutSync(mActivityRule, view, null /*runnable*/,
+                true /*forceLayout*/);
+
+        FutureTask<Point> clickLocationTask = new FutureTask<>(() -> {
             final int[] viewLocation = new int[2];
-            viewHolder[0].getLocationOnScreen(viewLocation);
+            view.getLocationOnScreen(viewLocation);
             // Set y position to the center of the view, to make sure it is away from the status bar
-            return new Point(viewLocation[0], viewLocation[1] + viewHolder[0].getHeight() / 2);
+            return new Point(viewLocation[0], viewLocation[1] + view.getHeight() / 2);
         });
-        mActivity.runOnUiThread(task);
-        Point viewLocation = task.get(5, TimeUnit.SECONDS);
+        mActivity.runOnUiThread(clickLocationTask);
+        Point viewLocation = clickLocationTask.get(5, TimeUnit.SECONDS);
         injectTap(viewLocation.x, viewLocation.y);
 
         List<MotionEvent> outsideEvents = listener.getOutsideEvents();
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/Android.mk b/tests/tests/syncmanager/Android.mk
index 14515dd..2532c81 100755
--- a/tests/tests/syncmanager/Android.mk
+++ b/tests/tests/syncmanager/Android.mk
@@ -27,6 +27,7 @@
     mockito-target-minus-junit4 \
     compatibility-device-util \
     ctstestrunner \
+    collector-device-lib \
     ub-uiautomator
 
 LOCAL_JAVA_LIBRARIES := android.test.runner.stubs android.test.base.stubs
diff --git a/tests/tests/syncmanager/AndroidManifest.xml b/tests/tests/syncmanager/AndroidManifest.xml
index 69ec2a8..9b5803b 100755
--- a/tests/tests/syncmanager/AndroidManifest.xml
+++ b/tests/tests/syncmanager/AndroidManifest.xml
@@ -25,6 +25,9 @@
     <uses-permission android:name="android.permission.READ_SYNC_SETTINGS" />
     <uses-permission android:name="android.permission.WRITE_SYNC_SETTINGS" />
 
+    <!-- We need it to write dump files in /sdcard/. See also AndroidTest.xml in the same directory -->
+    <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
+
     <instrumentation android:name="android.support.test.runner.AndroidJUnitRunner"
         android:targetPackage="android.content.syncmanager.cts"
         android:label="CTS tests for sync manager">
diff --git a/tests/tests/syncmanager/AndroidTest.xml b/tests/tests/syncmanager/AndroidTest.xml
index 0668895..d8fd6a3 100644
--- a/tests/tests/syncmanager/AndroidTest.xml
+++ b/tests/tests/syncmanager/AndroidTest.xml
@@ -35,10 +35,24 @@
         <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" >
         <option name="package" value="android.content.syncmanager.cts" />
         <option name="runtime-hint" value="10m00s" />
+
+        <!-- This is needed so that FilePullerLogCollector can read files in /sdcard/cts_text_dump/ -->
+        <option name="isolated-storage" value="false" />
     </test>
+
+    <!--
+      Pull out the dump files created by SystemUtil.runCommandAndDump().
+      Note in order for it to work, isolated-storage must be set to false in the test tag above.
+    -->
+    <metrics_collector class="com.android.tradefed.device.metric.FilePullerLogCollector">
+        <option name="directory-keys" value="/sdcard/cts_text_dump/" />
+    </metrics_collector>
+
 </configuration>
diff --git a/tests/tests/syncmanager/src/android/content/syncmanager/cts/CtsSyncManagerTest.java b/tests/tests/syncmanager/src/android/content/syncmanager/cts/CtsSyncManagerTest.java
index c70dcbe..1e1235f 100644
--- a/tests/tests/syncmanager/src/android/content/syncmanager/cts/CtsSyncManagerTest.java
+++ b/tests/tests/syncmanager/src/android/content/syncmanager/cts/CtsSyncManagerTest.java
@@ -22,7 +22,7 @@
 import static com.android.compatibility.common.util.BundleUtils.makeBundle;
 import static com.android.compatibility.common.util.ConnectivityUtils.assertNetworkConnected;
 import static com.android.compatibility.common.util.SettingsUtils.putGlobalSetting;
-import static com.android.compatibility.common.util.SystemUtil.runCommandAndPrintOnLogcat;
+import static com.android.compatibility.common.util.SystemUtil.runCommandAndDump;
 
 import static junit.framework.TestCase.assertEquals;
 
@@ -40,6 +40,7 @@
 import android.content.syncmanager.cts.SyncManagerCtsProto.Payload.Request.SetResult.Result;
 import android.content.syncmanager.cts.SyncManagerCtsProto.Payload.Response;
 import android.content.syncmanager.cts.SyncManagerCtsProto.Payload.SyncInvocation;
+import android.device.loggers.TestLogData;
 import android.os.Bundle;
 import android.support.test.InstrumentationRegistry;
 import android.support.test.filters.LargeTest;
@@ -76,11 +77,14 @@
     private static final int TIMEOUT_MS = 10 * 60 * 1000;
 
     @Rule
+    public TestLogData mLogs = new TestLogData();
+
+    @Rule
     public final OnFailureRule mDumpOnFailureRule = new OnFailureRule(TAG) {
         @Override
         protected void onTestFailure(Statement base, Description description, Throwable t) {
-            runCommandAndPrintOnLogcat(TAG, "dumpsys content");
-            runCommandAndPrintOnLogcat(TAG, "dumpsys jobscheduler");
+            runCommandAndDump(TAG, "dumpsys content", mLogs, "test failure");
+            runCommandAndDump(TAG, "dumpsys jobscheduler", mLogs, "test failure");
         }
     };
 
@@ -91,6 +95,10 @@
 
     @Before
     public void setUp() throws Exception {
+        Log.i(TAG, "Dumping initial state");
+        runCommandAndDump(TAG, "dumpsys content", mLogs, "setup");
+        runCommandAndDump(TAG, "dumpsys jobscheduler", mLogs, "setup");
+
         assertNetworkConnected(InstrumentationRegistry.getContext());
 
         BatteryUtils.runDumpsysBatteryUnplug();
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..e4f99b8 100644
--- a/tests/tests/telecom/Android.mk
+++ b/tests/tests/telecom/Android.mk
@@ -34,6 +34,7 @@
 LOCAL_SRC_FILES := $(call all-java-files-under, src)
 
 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
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/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/AndroidTest.xml b/tests/tests/telephony/AndroidTest.xml
index 43486b8..707ae6e 100644
--- a/tests/tests/telephony/AndroidTest.xml
+++ b/tests/tests/telephony/AndroidTest.xml
@@ -27,6 +27,8 @@
         <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"/>
     </target_preparer>
     <test class="com.android.tradefed.testtype.AndroidJUnitTest" >
         <option name="package" value="android.telephony.cts" />
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/src/android/telephony/cts/CarrierConfigManagerTest.java b/tests/tests/telephony/src/android/telephony/cts/CarrierConfigManagerTest.java
index 7323f30..901b47d 100644
--- a/tests/tests/telephony/src/android/telephony/cts/CarrierConfigManagerTest.java
+++ b/tests/tests/telephony/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.
      *
@@ -87,6 +106,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/src/android/telephony/cts/SmsManagerTest.java b/tests/tests/telephony/src/android/telephony/cts/SmsManagerTest.java
index 74f9d13..6eaf46e 100755
--- a/tests/tests/telephony/src/android/telephony/cts/SmsManagerTest.java
+++ b/tests/tests/telephony/src/android/telephony/cts/SmsManagerTest.java
@@ -16,10 +16,21 @@
 
 package android.telephony.cts;
 
+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;
@@ -28,16 +39,19 @@
 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.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;
@@ -45,8 +59,11 @@
 import java.io.InputStreamReader;
 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.TimeUnit;
 
 /**
  * Tests for {@link android.telephony.SmsManager}.
@@ -71,6 +88,8 @@
     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 TelephonyManager mTelephonyManager;
     private PackageManager mPackageManager;
@@ -91,8 +110,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
@@ -301,6 +321,167 @@
         }
     }
 
+    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();
diff --git a/tests/tests/telephony/src/android/telephony/cts/SubscriptionManagerTest.java b/tests/tests/telephony/src/android/telephony/cts/SubscriptionManagerTest.java
index f544694..f47b8d9 100644
--- a/tests/tests/telephony/src/android/telephony/cts/SubscriptionManagerTest.java
+++ b/tests/tests/telephony/src/android/telephony/cts/SubscriptionManagerTest.java
@@ -25,6 +25,7 @@
 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;
 
@@ -287,6 +288,66 @@
         assertOverrideSuccess(older, newer);
     }
 
+    @Test
+    public void testSubscriptionGrouping() throws Exception {
+        if (!isSupported()) return;
+
+        // Set subscription group with current sub Id. This should fail
+        // because we don't have MODIFY_PHONE_STATE or carrier privilege permission.
+        int[] subGroup = new int[] {mSubId};
+        try {
+            mSm.setSubscriptionGroup(subGroup);
+            fail();
+        } catch (SecurityException expected) {
+        }
+
+        // Getting subscriptions in group should return null as setSubscriptionGroup
+        // should fail.
+        assertNull(mSm.getSubscriptionsInGroup(mSubId));
+
+        // Remove from subscription group with current sub Id. This should fail
+        // because we don't have MODIFY_PHONE_STATE or carrier privilege permission.
+        try {
+            mSm.removeSubscriptionsFromGroup(subGroup);
+            fail();
+        } catch (SecurityException expected) {
+        }
+    }
+
+    @Test
+    public void testSettingOpportunisticSubscription() throws Exception {
+        if (!isSupported()) return;
+
+        // Set subscription to be opportunistic. This should fail
+        // because we don't have MODIFY_PHONE_STATE or carrier privilege permission.
+        try {
+            mSm.setOpportunistic(true, mSubId);
+            fail();
+        } catch (SecurityException expected) {
+        }
+
+        // Shouldn't crash.
+        SubscriptionInfo info = mSm.getActiveSubscriptionInfo(mSubId);
+        info.isOpportunistic();
+    }
+
+    @Test
+    public void testSettingSubscriptionMeteredNess() throws Exception {
+        if (!isSupported()) return;
+
+        // Set subscription to be un-metered. This should fail
+        // because we don't have MODIFY_PHONE_STATE or carrier privilege permission.
+        try {
+            mSm.setMetered(false, mSubId);
+            fail();
+        } catch (SecurityException expected) {
+        }
+
+        // Shouldn't crash.
+        SubscriptionInfo info = mSm.getActiveSubscriptionInfo(mSubId);
+        info.isMetered();
+    }
+
     private void assertOverrideSuccess(SubscriptionPlan... plans) {
         mSm.setSubscriptionPlans(mSubId, Arrays.asList(plans));
         mSm.setSubscriptionOverrideCongested(mSubId, false, 0);
diff --git a/tests/tests/telephony/src/android/telephony/cts/TelephonyManagerTest.java b/tests/tests/telephony/src/android/telephony/cts/TelephonyManagerTest.java
index d101899..5d17417 100644
--- a/tests/tests/telephony/src/android/telephony/cts/TelephonyManagerTest.java
+++ b/tests/tests/telephony/src/android/telephony/cts/TelephonyManagerTest.java
@@ -43,9 +43,11 @@
 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;
@@ -54,6 +56,7 @@
 import org.junit.runner.RunWith;
 
 import java.util.List;
+import java.util.Map;
 import java.util.regex.Pattern;
 
 /**
@@ -198,21 +201,30 @@
         mTelephonyManager.getCellLocation();
         mTelephonyManager.getSimCarrierId();
         mTelephonyManager.getSimCarrierIdName();
-        mTelephonyManager.getSimSerialNumber();
+        mTelephonyManager.getSimPreciseCarrierId();
+        mTelephonyManager.getSimPreciseCarrierIdName();
+        mTelephonyManager.getCarrierIdFromSimMccMnc();
+        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();
@@ -236,7 +248,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
@@ -272,7 +288,9 @@
      */
     @Test
     public void testGetDeviceId() {
-        verifyDeviceId(mTelephonyManager.getDeviceId());
+        String deviceId = ShellIdentityUtils.invokeMethodWithShellPermissions(mTelephonyManager,
+                (tm) -> tm.getDeviceId());
+        verifyDeviceId(deviceId);
     }
 
     /**
@@ -281,11 +299,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));
         }
     }
 
@@ -505,7 +528,8 @@
      */
     @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) {
@@ -524,15 +548,21 @@
         }
 
         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()));
     }
 
     /**
@@ -540,7 +570,8 @@
      */
     @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) {
@@ -568,7 +599,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);
                     }
@@ -577,8 +611,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()));
     }
 
     /**
@@ -670,4 +706,41 @@
         } 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;
+        }
+        boolean isEmergencyNumber = mTelephonyManager.isCurrentPotentialEmergencyNumber("911");
+        // TODO enhance it later
+    }
 }
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.mk b/tests/tests/text/Android.mk
index b56278c..ffa5f9e 100644
--- a/tests/tests/text/Android.mk
+++ b/tests/tests/text/Android.mk
@@ -29,6 +29,7 @@
     ctstestrunner \
     android-support-test \
     mockito-target-minus-junit4 \
+    ub-uiautomator
 
 LOCAL_SRC_FILES := $(call all-java-files-under, src)
 
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/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..7121238 100644
--- a/tests/tests/text/src/android/text/cts/MyanmarTest.java
+++ b/tests/tests/text/src/android/text/cts/MyanmarTest.java
@@ -17,75 +17,221 @@
 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 testMyanmarUnicodeRenders() {
+        assumeTrue(sHasBurmeseLocale);
+        assumeTrue(!sMymrLocales.isEmpty());
+
+        assertTrue("Should render Unicode text correctly in Myanmar Unicode locale",
+                isUnicodeRendersCorrectly(mContext, new LocaleList(sMymrLocales.get(0))));
+    }
+
+    @Test
+    public void testUnicodeRenders_withValidLocaleList() {
+        assumeTrue(sHasBurmeseLocale);
+        assumeTrue(!sMymrLocales.isEmpty());
+
+        final LocaleList[] testLocales = new LocaleList[]{
+                LocaleList.forLanguageTags("en-Latn-US"),
+                LocaleList.forLanguageTags("en-Latn"),
+                LocaleList.forLanguageTags("my-Mymr"),
+                LocaleList.forLanguageTags("my-Mymr,my-Qaag"),
+                LocaleList.forLanguageTags("my-Mymr-MM,my-Qaag-MM"),
+                LocaleList.forLanguageTags("en-Latn,my-Mymr"),
+                LocaleList.forLanguageTags("en-Latn-US,my-Mymr-MM"),
+                LocaleList.forLanguageTags("en-Mymr,my-Qaag"),
+                LocaleList.forLanguageTags("en-Mymr-MM,my-Qaag-MM"),
+        };
+
+        for (LocaleList localeList : testLocales) {
+            assertTrue("Should render Unicode text correctly in locale " + localeList.toString(),
+                    isUnicodeRendersCorrectly(mContext, localeList));
+        }
+
+    }
+
+    @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 isUnicodeRendersCorrectly(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 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 +239,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 adb8f97..932b1fc 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 c0ad4b6..9a7e39a 100644
--- a/tests/tests/text/src/android/text/util/cts/LinkifyTest.java
+++ b/tests/tests/text/src/android/text/util/cts/LinkifyTest.java
@@ -16,9 +16,15 @@
 
 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 static org.mockito.ArgumentMatchers.anyString;
+import static org.mockito.Mockito.spy;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.verify;
 
 import android.content.Context;
 import android.support.test.InstrumentationRegistry;
@@ -452,6 +458,68 @@
         assertEquals("https://android.com", spans[0].getURL());
     }
 
+    public class MyUrlSpan extends URLSpan {
+        public MyUrlSpan(String url) {
+            super(url);
+        }
+    }
+
+    public class MyUrlSpanFactory extends Linkify.UrlSpanFactory {
+        @Override
+        public URLSpan create(String url) {
+            return new MyUrlSpan(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 Linkify.UrlSpanFactory urlSpanFactory = spy(new MyUrlSpanFactory());
+
+        Linkify.addLinks(spannable, mask, urlSpanFactory);
+
+        verify(urlSpanFactory, times(3)).create(anyString());
+        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 Linkify.UrlSpanFactory urlSpanFactory = spy(new MyUrlSpanFactory());
+
+        Linkify.addLinks(spannable, LINKIFY_TEST_PATTERN, "test:", null /*schemes*/,
+                null /*matchFilter*/, null /*transformFilter*/, urlSpanFactory);
+
+        verify(urlSpanFactory, times(2)).create(anyString());
+
+        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);
+    }
+
+    @Test
+    public void testDefaultUrlSpanFactory() {
+        URLSpan urlSpan = new Linkify.UrlSpanFactory().create("some url");
+        assertNotNull(urlSpan);
+        assertEquals("some url", urlSpan.getURL());
+    }
+
     // WEB_URLS Related Tests
 
     @Test
@@ -935,6 +1003,17 @@
                 domain.length(), email);
     }
 
+    @Test
+    public void testAddLinks_unsupportedCharacters() {
+        String url = "moc.diordna.com";
+        verifyAddLinksWithWebUrlSucceeds(url + " should be linkified", url);
+
+        verifyAddLinksWithWebUrlFails("u202C character should not be linkified", "\u202C" + url);
+        verifyAddLinksWithWebUrlFails("u202D character should not be linkified", url + "\u202D");
+        verifyAddLinksWithWebUrlFails(
+                "u202E character should not be linkified", url + "moc\u202E.diordna.com");
+    }
+
     // Utility functions
     private static void verifyAddLinksWithWebUrlSucceeds(String msg, String url) {
         verifyAddLinksSucceeds(msg, url, Linkify.WEB_URLS);
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/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 c35a0c0..cadf0fe 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) {
@@ -392,5 +444,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 683c222..fb40e4b 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..604a35e 100644
--- a/tests/tests/uiautomation/AndroidTest.xml
+++ b/tests/tests/uiautomation/AndroidTest.xml
@@ -16,10 +16,14 @@
 <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" />
     <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 357ffb3..9b4ecad 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,7 +78,107 @@
         grantWriteSecureSettingsPermission(uiAutomation);
     }
 
-    @Presubmit
+    @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 */
+        }
+    }
+
+    @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 {
@@ -121,6 +237,7 @@
         }
     }
 
+    @Test
     public void testWindowContentFrameStatsNoAnimation() throws Exception {
         Activity activity = null;
         try {
@@ -172,6 +289,7 @@
     }
 
     @Presubmit
+    @Test
     public void testWindowAnimationFrameStats() throws Exception {
         Activity firstActivity = null;
         Activity secondActivity = null;
@@ -239,6 +357,7 @@
         }
     }
 
+    @Test
     public void testWindowAnimationFrameStatsNoAnimation() throws Exception {
         UiAutomation uiAutomation = getInstrumentation().getUiAutomation();
 
@@ -269,6 +388,7 @@
     }
 
     @Presubmit
+    @Test
     public void testUsingUiAutomationAfterDestroy_shouldThrowException() {
         UiAutomation uiAutomation = getInstrumentation().getUiAutomation();
         uiAutomation.destroy();
@@ -280,8 +400,8 @@
     }
 
     @AppModeFull
-    public void testDontSuppressAccessibility_canStartA11yService() throws IOException,
-            InterruptedException {
+    @Test
+    public void testDontSuppressAccessibility_canStartA11yService() throws Exception {
         turnAccessibilityOff();
         try {
             getInstrumentation()
@@ -294,7 +414,8 @@
     }
 
     @AppModeFull
-    public void testServiceWithNoFlags_shutsDownA11yService() throws IOException {
+    @Test
+    public void testServiceWithNoFlags_shutsDownA11yService() throws Exception {
         turnAccessibilityOff();
         try {
             UiAutomation uiAutomation = getInstrumentation()
@@ -311,8 +432,9 @@
     }
 
     @AppModeFull
+    @Test
     public void testServiceSupressingA11yServices_a11yServiceStartsWhenDestroyed()
-            throws IOException, InterruptedException {
+            throws Exception {
         turnAccessibilityOff();
         try {
             UiAutomation uiAutomation = getInstrumentation()
@@ -331,8 +453,9 @@
     }
 
     @AppModeFull
+    @Test
     public void testServiceSupressingA11yServices_a11yServiceStartsWhenFlagsChange()
-            throws IOException, InterruptedException {
+            throws Exception {
         turnAccessibilityOff();
         try {
             getInstrumentation()
@@ -377,7 +500,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);
     }
@@ -512,4 +635,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/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..f2185b4 100644
--- a/tests/tests/uirendering/src/android/uirendering/cts/testclasses/ExactCanvasTests.java
+++ b/tests/tests/uirendering/src/android/uirendering/cts/testclasses/ExactCanvasTests.java
@@ -16,6 +16,7 @@
 
 package android.uirendering.cts.testclasses;
 
+import android.graphics.BlendMode;
 import android.graphics.Canvas;
 import android.graphics.Color;
 import android.graphics.Paint;
@@ -196,6 +197,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 +262,23 @@
                 .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);
+    }
 }
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..be3bcc3
--- /dev/null
+++ b/tests/tests/uirendering/src/android/uirendering/cts/testclasses/ForceDarkTests.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.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.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 ->
+                        ((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);
+                    ((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..1900919
--- /dev/null
+++ b/tests/tests/uirendering/src/android/uirendering/cts/testclasses/RenderNodeTests.java
@@ -0,0 +1,210 @@
+/*
+ * Copyright (C) 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR 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.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 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);
+        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 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()));
+        }
+    }
+}
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/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..68d14a7 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>
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/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/src/android/view/animation/cts/AnimationTest.java b/tests/tests/view/src/android/view/animation/cts/AnimationTest.java
index d3bc8e0..af69a7f 100644
--- a/tests/tests/view/src/android/view/animation/cts/AnimationTest.java
+++ b/tests/tests/view/src/android/view/animation/cts/AnimationTest.java
@@ -700,7 +700,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 +708,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/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/SurfaceViewSyncTest.java b/tests/tests/view/src/android/view/cts/SurfaceViewSyncTest.java
index 2a14a8d..e3744fd 100644
--- a/tests/tests/view/src/android/view/cts/SurfaceViewSyncTest.java
+++ b/tests/tests/view/src/android/view/cts/SurfaceViewSyncTest.java
@@ -28,6 +28,7 @@
 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;
@@ -60,6 +61,7 @@
 @RunWith(AndroidJUnit4.class)
 @LargeTest
 @SuppressLint("RtlHardcoded")
+@RequiresDevice
 public class SurfaceViewSyncTest {
     private static final String TAG = "SurfaceViewSyncTests";
 
diff --git a/tests/tests/view/src/android/view/cts/TextureViewCtsActivity.java b/tests/tests/view/src/android/view/cts/TextureViewCtsActivity.java
index 8b0d4c7..a67c178 100644
--- a/tests/tests/view/src/android/view/cts/TextureViewCtsActivity.java
+++ b/tests/tests/view/src/android/view/cts/TextureViewCtsActivity.java
@@ -355,6 +355,9 @@
                     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_SRGB_KHR:
                         eglColorSpaceString = "EGL_KHR_gl_colorspace";
                         break;
diff --git a/tests/tests/view/src/android/view/cts/TextureViewTest.java b/tests/tests/view/src/android/view/cts/TextureViewTest.java
index 431b27d..c26782c 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;
@@ -60,6 +61,7 @@
 
     static final int EGL_GL_COLORSPACE_SRGB_KHR = 0x3089;
     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_SCRGB_LINEAR_EXT = 0x3350;
 
     @Rule
@@ -218,15 +220,17 @@
                 screenshot.getPixel(texturePos.right - 10, texturePos.bottom - 10));
     }
 
+    @Ignore // Disabled temporarily, b/119504473
     @Test
     public void testGetBitmap_8888_P3() throws Throwable {
         testGetBitmap(EGL_GL_COLORSPACE_DISPLAY_P3_EXT, ColorSpace.Named.DISPLAY_P3, false,
                 new FP16Compare(ColorSpace.Named.EXTENDED_SRGB));
     }
 
+    @Ignore // Disabled temporarily, b/119504473
     @Test
     public void testGetBitmap_FP16_P3() throws Throwable {
-        testGetBitmap(EGL_GL_COLORSPACE_DISPLAY_P3_EXT, ColorSpace.Named.DISPLAY_P3, true,
+        testGetBitmap(EGL_GL_COLORSPACE_DISPLAY_P3_LINEAR_EXT, ColorSpace.Named.DISPLAY_P3, true,
                 new FP16Compare(ColorSpace.Named.EXTENDED_SRGB));
     }
 
@@ -236,18 +240,111 @@
                 true, new FP16Compare(ColorSpace.Named.EXTENDED_SRGB));
     }
 
+    @Ignore // Disabled temporarily, b/119504473
     @Test
     public void testGet565Bitmap_SRGB() throws Throwable {
-        testGetBitmap(EGL_GL_COLORSPACE_SRGB_KHR, ColorSpace.Named.SRGB, true,
+        testGetBitmap(EGL_GL_COLORSPACE_SRGB_KHR, ColorSpace.Named.SRGB, false,
                 new SRGBCompare(Bitmap.Config.RGB_565));
     }
 
+    @Ignore // Disabled temporarily, b/119504473
     @Test
     public void testGetBitmap_SRGB() throws Throwable {
-        testGetBitmap(EGL_GL_COLORSPACE_SRGB_KHR, ColorSpace.Named.SRGB, true,
+        testGetBitmap(EGL_GL_COLORSPACE_SRGB_KHR, ColorSpace.Named.SRGB, false,
                 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();
@@ -364,6 +461,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..5389608 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.
@@ -28,6 +28,7 @@
 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;
@@ -43,7 +44,6 @@
 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,6 +60,7 @@
 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;
@@ -72,10 +73,14 @@
 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;
@@ -1006,6 +1011,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 +2105,6 @@
 
     class MockCanvas extends Canvas {
 
-        public boolean mIsSaveCalled;
         public int mLeft;
         public int mTop;
         public int mRight;
@@ -1948,18 +2126,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 +2153,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 +2161,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 +2777,40 @@
         assertEquals(5, resetResolvedDrawablesCount);
     }
 
+    @UiThreadTest
+    @Test
+    public void testLayoutNotCalledWithSuppressLayoutTrue() {
+        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.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);
+    }
+
     static class MockTextView extends TextView {
 
         public boolean isClearFocusCalled;
@@ -3062,8 +3260,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 +3274,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 +3378,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/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/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 41ad595..76b1a1c 100644
--- a/tests/tests/view/src/android/view/cts/WindowTest.java
+++ b/tests/tests/view/src/android/view/cts/WindowTest.java
@@ -980,17 +980,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/CapturedActivity.java b/tests/tests/view/src/android/view/cts/surfacevalidator/CapturedActivity.java
index 4fc5e04..e1b2353 100644
--- a/tests/tests/view/src/android/view/cts/surfacevalidator/CapturedActivity.java
+++ b/tests/tests/view/src/android/view/cts/surfacevalidator/CapturedActivity.java
@@ -214,7 +214,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) {
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..6729491
--- /dev/null
+++ b/tests/tests/view/src/android/view/inspector/cts/IntEnumMappingTest.java
@@ -0,0 +1,45 @@
+/*
+ * 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.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 final class IntEnumMappingTest {
+    @Test
+    public void testMapping() {
+        IntEnumMapping mapping = new IntEnumMapping.Builder()
+                .addValue("ONE", 1)
+                .addValue("TWO", 2)
+                .build();
+        assertNull(mapping.nameOf(0));
+        assertEquals("ONE", mapping.nameOf(1));
+        assertEquals("TWO", mapping.nameOf(2));
+    }
+}
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..ba3a3af
--- /dev/null
+++ b/tests/tests/view/src/android/view/inspector/cts/IntFlagMappingTest.java
@@ -0,0 +1,81 @@
+/*
+ * 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.assertArrayEquals;
+
+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;
+
+/**
+ * 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();
+
+        assertArrayEquals(new String[0], mapping.namesOf(0));
+        assertArrayEquals(new String[] {"ONE"}, mapping.namesOf(1));
+        assertArrayEquals(new String[] {"TWO"}, mapping.namesOf(2));
+        assertArrayEquals(new String[] {"ONE", "TWO"}, mapping.namesOf(3));
+        assertArrayEquals(new String[0], mapping.namesOf(4));
+    }
+
+    @Test
+    public void testMutuallyExclusiveFlags() {
+        IntFlagMapping mapping = new IntFlagMapping.Builder()
+                .addFlag("ONE", 1, 3)
+                .addFlag("TWO", 2, 3)
+                .build();
+
+
+        assertArrayEquals(new String[0], mapping.namesOf(0));
+        assertArrayEquals(new String[] {"ONE"}, mapping.namesOf(1));
+        assertArrayEquals(new String[] {"TWO"}, mapping.namesOf(2));
+        assertArrayEquals(new String[0], mapping.namesOf(3));
+        assertArrayEquals(new String[0], mapping.namesOf(4));
+    }
+
+    @Test
+    public void testMixedFlags() {
+        IntFlagMapping mapping = new IntFlagMapping.Builder()
+                .addFlag("ONE", 1, 3)
+                .addFlag("TWO", 2, 3)
+                .addFlag("FOUR", 4)
+                .build();
+
+
+        assertArrayEquals(new String[0], mapping.namesOf(0));
+        assertArrayEquals(new String[] {"ONE"}, mapping.namesOf(1));
+        assertArrayEquals(new String[] {"TWO"}, mapping.namesOf(2));
+        assertArrayEquals(new String[0], mapping.namesOf(3));
+        assertArrayEquals(new String[] {"FOUR"}, mapping.namesOf(4));
+        assertArrayEquals(new String[] {"ONE", "FOUR"}, mapping.namesOf(5));
+        assertArrayEquals(new String[] {"TWO", "FOUR"}, mapping.namesOf(6));
+        assertArrayEquals(new String[] {"FOUR"}, mapping.namesOf(7));
+    }
+}
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..54f6166
--- /dev/null
+++ b/tests/tests/view/src/android/view/textclassifier/cts/ConversationActionsTest.java
@@ -0,0 +1,362 @@
+/*
+ * Copyright (C) 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR 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.ConversationActions;
+
+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() {
+        ConversationActions.TypeConfig typeConfig =
+                new ConversationActions.TypeConfig.Builder()
+                        .setIncludedTypes(
+                                Collections.singletonList(ConversationActions.TYPE_OPEN_URL))
+                        .setExcludedTypes(
+                                Collections.singletonList(ConversationActions.TYPE_CALL_PHONE))
+                        .build();
+
+        ConversationActions.TypeConfig recovered =
+                parcelizeDeparcelize(typeConfig, ConversationActions.TypeConfig.CREATOR);
+
+        assertFullTypeConfig(typeConfig);
+        assertFullTypeConfig(recovered);
+    }
+
+    @Test
+    public void testTypeConfig_full_notIncludeTypesFromTextClassifier() {
+        ConversationActions.TypeConfig typeConfig =
+                new ConversationActions.TypeConfig.Builder()
+                        .includeTypesFromTextClassifier(false)
+                        .setIncludedTypes(
+                                Collections.singletonList(ConversationActions.TYPE_OPEN_URL))
+                        .setExcludedTypes(
+                                Collections.singletonList(ConversationActions.TYPE_CALL_PHONE))
+                        .build();
+
+        ConversationActions.TypeConfig recovered =
+                parcelizeDeparcelize(typeConfig, ConversationActions.TypeConfig.CREATOR);
+
+        assertFullTypeConfig_notIncludeTypesFromTextClassifier(typeConfig);
+        assertFullTypeConfig_notIncludeTypesFromTextClassifier(recovered);
+    }
+
+    @Test
+    public void testTypeConfig_minimal() {
+        ConversationActions.TypeConfig typeConfig =
+                new ConversationActions.TypeConfig.Builder().build();
+
+        ConversationActions.TypeConfig recovered =
+                parcelizeDeparcelize(typeConfig, ConversationActions.TypeConfig.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();
+        ConversationActions.TypeConfig typeConfig =
+                new ConversationActions.TypeConfig.Builder()
+                        .includeTypesFromTextClassifier(false)
+                        .build();
+        ConversationActions.Request request =
+                new ConversationActions.Request.Builder(Collections.singletonList(message))
+                        .setConversationId(CONVERSATION_ID)
+                        .setHints(Collections.singletonList(ConversationActions.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() {
+        ConversationActions.ConversationAction conversationAction =
+                new ConversationActions.ConversationAction.Builder(
+                        ConversationActions.TYPE_CALL_PHONE)
+                        .build();
+
+        ConversationActions.ConversationAction recovered =
+                parcelizeDeparcelize(conversationAction,
+                        ConversationActions.ConversationAction.CREATOR);
+
+        assertMinimalConversationAction(conversationAction);
+        assertMinimalConversationAction(recovered);
+    }
+
+    @Test
+    public void testConversationAction_full() {
+        ConversationActions.ConversationAction conversationAction =
+                new ConversationActions.ConversationAction.Builder(
+                        ConversationActions.TYPE_CALL_PHONE)
+                        .setConfidenceScore(1.0f)
+                        .setTextReply(TEXT)
+                        .setAction(REMOTE_ACTION)
+                        .setExtras(EXTRAS)
+                        .build();
+
+        ConversationActions.ConversationAction recovered =
+                parcelizeDeparcelize(conversationAction,
+                        ConversationActions.ConversationAction.CREATOR);
+
+        assertFullConversationAction(conversationAction);
+        assertFullConversationAction(recovered);
+    }
+
+    @Test
+    public void testConversationActions_full() {
+        ConversationActions.ConversationAction conversationAction =
+                new ConversationActions.ConversationAction.Builder(
+                        ConversationActions.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() {
+        ConversationActions.ConversationAction conversationAction =
+                new ConversationActions.ConversationAction.Builder(
+                        ConversationActions.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(ConversationActions.TypeConfig typeConfig) {
+        List<String> extraTypesFromTextClassifier = Arrays.asList(
+                ConversationActions.TYPE_CALL_PHONE,
+                ConversationActions.TYPE_CREATE_REMINDER);
+
+        Collection<String> resolvedTypes = typeConfig.resolveTypes(extraTypesFromTextClassifier);
+
+        assertThat(typeConfig.shouldIncludeTypesFromTextClassifier()).isTrue();
+        assertThat(typeConfig.resolveTypes(Collections.emptyList()))
+                .containsExactly(ConversationActions.TYPE_OPEN_URL);
+        assertThat(resolvedTypes).containsExactly(
+                ConversationActions.TYPE_OPEN_URL, ConversationActions.TYPE_CREATE_REMINDER);
+    }
+
+    private void assertFullTypeConfig_notIncludeTypesFromTextClassifier(
+            ConversationActions.TypeConfig typeConfig) {
+        List<String> extraTypesFromTextClassifier = Arrays.asList(
+                ConversationActions.TYPE_CALL_PHONE,
+                ConversationActions.TYPE_CREATE_REMINDER);
+
+        Collection<String> resolvedTypes = typeConfig.resolveTypes(extraTypesFromTextClassifier);
+
+        assertThat(typeConfig.shouldIncludeTypesFromTextClassifier()).isFalse();
+        assertThat(typeConfig.resolveTypes(Collections.emptyList()))
+                .containsExactly(ConversationActions.TYPE_OPEN_URL);
+        assertThat(resolvedTypes).containsExactly(ConversationActions.TYPE_OPEN_URL);
+    }
+
+    private void assertMinimalTypeConfig(ConversationActions.TypeConfig typeConfig) {
+        assertThat(typeConfig.shouldIncludeTypesFromTextClassifier()).isTrue();
+        assertThat(typeConfig.resolveTypes(Collections.emptyList())).isEmpty();
+        assertThat(typeConfig.resolveTypes(
+                Collections.singletonList(ConversationActions.TYPE_OPEN_URL))).containsExactly(
+                ConversationActions.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.HINT_FOR_IN_APP);
+        assertThat(request.getMaxSuggestions()).isEqualTo(10);
+        assertThat(request.getTypeConfig().shouldIncludeTypesFromTextClassifier()).isFalse();
+        assertThat(request.getConversationId()).isEqualTo(CONVERSATION_ID);
+    }
+
+    private void assertMinimalConversationAction(
+            ConversationActions.ConversationAction conversationAction) {
+        assertThat(conversationAction.getAction()).isNull();
+        assertThat(conversationAction.getConfidenceScore()).isWithin(FLOAT_TOLERANCE).of(0.0f);
+        assertThat(conversationAction.getType()).isEqualTo(ConversationActions.TYPE_CALL_PHONE);
+    }
+
+    private void assertFullConversationAction(
+            ConversationActions.ConversationAction conversationAction) {
+        assertThat(conversationAction.getAction().getTitle()).isEqualTo(TEXT);
+        assertThat(conversationAction.getConfidenceScore()).isWithin(FLOAT_TOLERANCE).of(1.0f);
+        assertThat(conversationAction.getType()).isEqualTo(ConversationActions.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(ConversationActions.TYPE_CALL_PHONE);
+        assertThat(conversationActions.getId()).isNull();
+    }
+
+    private void assertFullConversationActions(ConversationActions conversationActions) {
+        assertThat(conversationActions.getConversationActions()).hasSize(1);
+        assertThat(conversationActions.getConversationActions().get(0).getType())
+                .isEqualTo(ConversationActions.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..1cd65b8 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,28 @@
 
 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.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 +49,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 +76,16 @@
             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 MESSAGE =
+            new ConversationActions.Message.Builder(ConversationActions.Message.PERSON_USER_REMOTE)
+                    .setText(TEXT)
+                    .build();
+    private static final ConversationActions.Request CONVERSATION_ACTIONS_REQUEST =
+            new ConversationActions.Request.Builder(Arrays.asList(MESSAGE)).build();
 
     private TextClassificationManager mManager;
     private TextClassifier mClassifier;
@@ -129,6 +153,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 +199,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 +257,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<ConversationActions.ConversationAction> conversationActionsList =
+                conversationActions.getConversationActions();
+        assertNotNull(conversationActionsList);
+        for (ConversationActions.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..f1ca661
--- /dev/null
+++ b/tests/tests/view/src/android/view/textclassifier/cts/TextClassifierEventTest.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.view.textclassifier.cts;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import android.os.Bundle;
+import android.os.Parcel;
+import android.support.test.InstrumentationRegistry;
+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 {
+
+    @Test
+    public void testMinimumEvent() {
+        final TextClassifierEvent event = new TextClassifierEvent.Builder(
+                TextClassifierEvent.CATEGORY_UNDEFINED, TextClassifierEvent.TYPE_UNDEFINED)
+                .build();
+
+        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.getEntityType()).isNull();
+        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()).isEqualTo(Bundle.EMPTY);
+        assertThat(event.getEventContext()).isNull();
+    }
+
+    @Test
+    public void testFullEvent() {
+        final Bundle extra = new Bundle();
+        extra.putString("key", "value");
+        final long now = System.currentTimeMillis();
+        final String resultId = "androidtc-en-v606-1234";
+        final TextClassifierEvent event = new TextClassifierEvent.Builder(
+                TextClassifierEvent.CATEGORY_LINKIFY,
+                TextClassifierEvent.TYPE_LINK_CLICKED)
+                .setEventIndex(2)
+                .setEventTime(now)
+                .setEntityType(TextClassifier.TYPE_ADDRESS)
+                .setRelativeWordStartIndex(1)
+                .setRelativeWordEndIndex(2)
+                .setRelativeSuggestedWordStartIndex(-1)
+                .setRelativeSuggestedWordEndIndex(3)
+                .setLanguage("en")
+                .setResultId(resultId)
+                .setActionIndices(1, 2, 5)
+                .setExtras(extra)
+                .setEventContext(new TextClassificationContext.Builder(
+                        "pkg", TextClassifier.WIDGET_TYPE_TEXTVIEW)
+                        .setWidgetVersion(TextView.class.getName())
+                        .build())
+                .build();
+
+        assertThat(event.getEventCategory()).isEqualTo(TextClassifierEvent.CATEGORY_LINKIFY);
+        assertThat(event.getEventType()).isEqualTo(TextClassifierEvent.TYPE_LINK_CLICKED);
+        assertThat(event.getEventIndex()).isEqualTo(2);
+        assertThat(event.getEventTime()).isEqualTo(now);
+        assertThat(event.getEntityType()).isEqualTo(TextClassifier.TYPE_ADDRESS);
+        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(resultId);
+        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());
+    }
+
+    @Test
+    public void testParcelUnparcel() {
+        final Bundle extra = new Bundle();
+        extra.putString("k", "v");
+        final TextClassifierEvent event = new TextClassifierEvent.Builder(
+                TextClassifierEvent.CATEGORY_SELECTION,
+                TextClassifierEvent.TYPE_SELECTION_MODIFIED)
+                .setEventIndex(1)
+                .setEventTime(1000)
+                .setEntityType(TextClassifier.TYPE_DATE)
+                .setRelativeWordStartIndex(4)
+                .setRelativeWordEndIndex(3)
+                .setRelativeSuggestedWordStartIndex(2)
+                .setRelativeSuggestedWordEndIndex(1)
+                .setLanguage("de")
+                .setResultId("id")
+                .setActionIndices(3)
+                .setExtras(extra)
+                .setEventContext(new TextClassificationContext.Builder(
+                        InstrumentationRegistry.getTargetContext().getPackageName(),
+                        TextClassifier.WIDGET_TYPE_UNSELECTABLE_TEXTVIEW)
+                        .setWidgetVersion("notification")
+                        .build())
+                .build();
+
+        final Parcel parcel = Parcel.obtain();
+        event.writeToParcel(parcel, event.describeContents());
+        parcel.setDataPosition(0);
+        final TextClassifierEvent result = TextClassifierEvent.CREATOR.createFromParcel(parcel);
+
+        assertThat(result.getEventCategory()).isEqualTo(TextClassifierEvent.CATEGORY_SELECTION);
+        assertThat(result.getEventType()).isEqualTo(TextClassifierEvent.TYPE_SELECTION_MODIFIED);
+        assertThat(result.getEventIndex()).isEqualTo(1);
+        assertThat(result.getEventTime()).isEqualTo(1000);
+        assertThat(result.getEntityType()).isEqualTo(TextClassifier.TYPE_DATE);
+        assertThat(result.getRelativeWordStartIndex()).isEqualTo(4);
+        assertThat(result.getRelativeWordEndIndex()).isEqualTo(3);
+        assertThat(result.getRelativeSuggestedWordStartIndex()).isEqualTo(2);
+        assertThat(result.getRelativeSuggestedWordEndIndex()).isEqualTo(1);
+        assertThat(result.getLanguage()).isEqualTo("de");
+        assertThat(result.getResultId()).isEqualTo("id");
+        assertThat(result.getActionIndices()).asList().containsExactly(3);
+        assertThat(result.getExtras().get("k")).isEqualTo("v");
+        assertThat(result.getEventContext().getPackageName())
+                .isEqualTo(InstrumentationRegistry.getTargetContext().getPackageName());
+        assertThat(result.getEventContext().getWidgetType())
+                .isEqualTo(TextClassifier.WIDGET_TYPE_UNSELECTABLE_TEXTVIEW);
+        assertThat(result.getEventContext().getWidgetVersion()).isEqualTo("notification");
+    }
+}
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/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/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..3a35959 100644
--- a/tests/tests/webkit/src/android/webkit/cts/PostMessageTest.java
+++ b/tests/tests/webkit/src/android/webkit/cts/PostMessageTest.java
@@ -16,6 +16,9 @@
 
 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;
@@ -25,10 +28,13 @@
 
 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;
 
@@ -99,16 +105,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 +145,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 +164,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,7 +179,7 @@
         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);
+        final BlockingQueue<String> queue = new ArrayBlockingQueue<>(messageCount);
         runTestOnUiThread(new Runnable() {
             @Override
             public void run() {
@@ -158,17 +189,27 @@
                 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();
+                        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()) {
@@ -211,6 +252,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";
@@ -234,4 +280,76 @@
         });
         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());
+
+        runTestOnUiThread(new Runnable() {
+            @Override
+            public void run() {
+                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();
+
+        runTestOnUiThread(new Runnable() {
+            @Override
+            public void run() {
+                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..38f4510 100644
--- a/tests/tests/webkit/src/android/webkit/cts/ServiceWorkerClientTest.java
+++ b/tests/tests/webkit/src/android/webkit/cts/ServiceWorkerClientTest.java
@@ -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..07b702f
--- /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(this, 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/WebBackForwardListTest.java b/tests/tests/webkit/src/android/webkit/cts/WebBackForwardListTest.java
index eb5b413..fedf521 100644
--- a/tests/tests/webkit/src/android/webkit/cts/WebBackForwardListTest.java
+++ b/tests/tests/webkit/src/android/webkit/cts/WebBackForwardListTest.java
@@ -108,7 +108,4 @@
         }.run();
     }
 
-    public void testClone() {
-    }
-
 }
diff --git a/tests/tests/webkit/src/android/webkit/cts/WebSettingsTest.java b/tests/tests/webkit/src/android/webkit/cts/WebSettingsTest.java
index 8cfaec3..c204ea6 100644
--- a/tests/tests/webkit/src/android/webkit/cts/WebSettingsTest.java
+++ b/tests/tests/webkit/src/android/webkit/cts/WebSettingsTest.java
@@ -39,13 +39,13 @@
 
 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;
 
@@ -417,15 +417,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 +439,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 +528,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;
@@ -742,6 +747,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 +1068,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..e02f8ac 100644
--- a/tests/tests/webkit/src/android/webkit/cts/WebViewClientTest.java
+++ b/tests/tests/webkit/src/android/webkit/cts/WebViewClientTest.java
@@ -40,6 +40,7 @@
 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;
@@ -90,6 +91,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 +106,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()) {
@@ -265,6 +277,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 +296,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;
@@ -610,20 +632,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 +670,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 +715,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/WebViewTest.java b/tests/tests/webkit/src/android/webkit/cts/WebViewTest.java
index f5f3139..71c5611 100644
--- a/tests/tests/webkit/src/android/webkit/cts/WebViewTest.java
+++ b/tests/tests/webkit/src/android/webkit/cts/WebViewTest.java
@@ -81,6 +81,7 @@
 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 junit.framework.Assert;
 
@@ -101,7 +102,6 @@
 import java.util.concurrent.atomic.AtomicBoolean;
 import java.util.concurrent.atomic.AtomicReference;
 import java.util.concurrent.Callable;
-import java.util.concurrent.CountDownLatch;
 import java.util.concurrent.FutureTask;
 import java.util.concurrent.Semaphore;
 import java.util.concurrent.TimeUnit;
@@ -979,18 +979,17 @@
 
         assertFalse(mJsInterfaceWasCalled.get());
 
-        final CountDownLatch resultLatch = new CountDownLatch(1);
+        final SettableFuture<String> javascriptResultFuture = SettableFuture.create();
         mOnUiThread.evaluateJavascript(
                 "try {dummy.call(); 'fail'; } catch (exception) { 'pass'; } ",
                 new ValueCallback<String>() {
                         @Override
                         public void onReceiveValue(String result) {
-                            assertEquals("\"pass\"", result);
-                            resultLatch.countDown();
+                            javascriptResultFuture.set(result);
                         }
                     });
 
-        assertTrue(resultLatch.await(TEST_TIMEOUT, TimeUnit.MILLISECONDS));
+        assertEquals("\"pass\"", WebkitUtils.waitForFuture(javascriptResultFuture));
         assertTrue(mJsInterfaceWasCalled.get());
     }
 
@@ -2290,7 +2289,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 +2303,7 @@
                 this.mimeType = mimetype;
                 this.contentLength = contentLength;
                 this.contentDisposition = contentDisposition;
-                resultLatch.countDown();
+                downloadStartFuture.set(null);
             }
         }
 
@@ -2327,7 +2326,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);
@@ -2635,47 +2634,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 +2673,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 +2696,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 +2750,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 +2822,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,22 +2858,26 @@
         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,
@@ -3060,6 +3078,11 @@
         }
     }
 
+    /**
+     * 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;
diff --git a/tests/tests/webkit/src/android/webkit/cts/WebkitUtils.java b/tests/tests/webkit/src/android/webkit/cts/WebkitUtils.java
new file mode 100644
index 0000000..449eee7
--- /dev/null
+++ b/tests/tests/webkit/src/android/webkit/cts/WebkitUtils.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.webkit.cts;
+
+import java.util.concurrent.BlockingQueue;
+import java.util.concurrent.ExecutionException;
+import java.util.concurrent.Future;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.TimeoutException;
+
+import junit.framework.Assert;
+
+/**
+ * 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 {
+
+    private static final long TEST_TIMEOUT_MS = 20000L; // 20s.
+
+    /**
+     * Waits for {@code future} and returns its value (or times out).
+     */
+    public static <T> T waitForFuture(Future<T> future) throws InterruptedException,
+             ExecutionException,
+             TimeoutException {
+        // TODO(ntfschr): consider catching ExecutionException and throwing e.getCause().
+        return future.get(TEST_TIMEOUT_MS, TimeUnit.MILLISECONDS);
+    }
+
+    /**
+     * Takes an element out of the {@link BlockingQueue} (or times out).
+     */
+    public static <T> T waitForNextQueueElement(BlockingQueue<T> queue) throws InterruptedException,
+             TimeoutException {
+        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;
+    }
+
+    // Do not instantiate this class.
+    private WebkitUtils() {}
+}
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_isHorizontallyScrolling_layout.xml b/tests/tests/widget/res/layout/textview_isHorizontallyScrolling_layout.xml
new file mode 100644
index 0000000..e2d3aaa
--- /dev/null
+++ b/tests/tests/widget/res/layout/textview_isHorizontallyScrolling_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..46d5ca3 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,270 @@
         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 forcePositionWithinBounds = false;
+        final Drawable overlay = new ColorDrawable(Color.BLUE);
+
+        final Magnifier.Builder builder = new Magnifier.Builder(mView)
+                .setSize(magnifierWidth, magnifierHeight)
+                .setZoom(zoom)
+                .setDefaultSourceToMagnifierOffset(sourceToMagnifierHorizontalOffset,
+                        sourceToMagnifierVerticalOffset)
+                .setCornerRadius(cornerRadius)
+                .setZoom(zoom)
+                .setElevation(elevation)
+                .setOverlay(overlay)
+                .setForcePositionWithinWindowSystemInsetsBounds(forcePositionWithinBounds);
+        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(forcePositionWithinBounds,
+                magnifier.isForcePositionWithinWindowSystemInsetsBounds());
+        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).setZoom(0f);
+    }
+
+    @Test(expected = IllegalArgumentException.class)
+    public void testBuilder_throwsException_whenZoomIsNegative() {
+        new Magnifier.Builder(mView).setZoom(-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 forcePositionWithinBounds = true;
+        assertEquals(forcePositionWithinBounds,
+                magnifier.isForcePositionWithinWindowSystemInsetsBounds());
+        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 forcePositionWithinBounds = true;
+        assertEquals(forcePositionWithinBounds,
+                magnifier.isForcePositionWithinWindowSystemInsetsBounds());
+        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 +403,587 @@
         });
 
         // 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)
+                .setForcePositionWithinWindowSystemInsetsBounds(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)
+                .setForcePositionWithinWindowSystemInsetsBounds(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)
+                .setZoom(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)
+                .setZoom(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_respectsMaxInViewBounds() 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)
+                .setZoom(10f) /* 10x10 source size */
+                .setSourceBounds(
+                        Magnifier.SOURCE_BOUND_MAX_IN_VIEW,
+                        Magnifier.SOURCE_BOUND_MAX_IN_VIEW,
+                        Magnifier.SOURCE_BOUND_MAX_IN_VIEW,
+                        Magnifier.SOURCE_BOUND_MAX_IN_VIEW
+                );
+
+        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 to have been pulled to coincide with (0, 0) of the view.
+        showMagnifier(0, 0);
+        Point sourcePosition = mMagnifier.getSourcePosition();
+        assertNotNull(sourcePosition);
+        assertEquals(viewPosition[0], sourcePosition.x);
+        assertEquals(viewPosition[1], sourcePosition.y);
+
+        showMagnifier(view.getWidth(), view.getHeight());
+        sourcePosition = mMagnifier.getSourcePosition();
+        assertNotNull(sourcePosition);
+        assertEquals(viewPosition[0] + view.getWidth() - mMagnifier.getSourceWidth(),
+                sourcePosition.x);
+        assertEquals(viewPosition[1] + view.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)
+                .setZoom(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)
+                .setZoom(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)
+                .setZoom(1f) /* source double the size of the view + right/bottom insets */
+                .setSourceBounds(/* invalid bounds */
+                        Magnifier.SOURCE_BOUND_MAX_IN_VIEW,
+                        Magnifier.SOURCE_BOUND_MAX_IN_VIEW,
+                        Magnifier.SOURCE_BOUND_MAX_IN_VIEW,
+                        Magnifier.SOURCE_BOUND_MAX_IN_VIEW
+                );
+
+        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)
+                .setZoom(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)
+                .setZoom(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 +993,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..4384c0c 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();
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/TextViewIsHorizontallyScrollingTest.java b/tests/tests/widget/src/android/widget/cts/TextViewIsHorizontallyScrollingTest.java
new file mode 100644
index 0000000..f3e3847
--- /dev/null
+++ b/tests/tests/widget/src/android/widget/cts/TextViewIsHorizontallyScrollingTest.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#isHorizontallyScrolling}.
+ */
+@SmallTest
+@RunWith(AndroidJUnit4.class)
+public class TextViewIsHorizontallyScrollingTest {
+    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_isHorizontallyScrolling_layout,
+                null);
+    }
+
+    @Test
+    public void testIsHorizontallyScrollingDefaultIsFalse() {
+        final TextView textView = mViewGroup.findViewById(
+                R.id.textView_scrollHorizontally_default);
+
+        assertFalse(textView.isHorizontallyScrolling());
+    }
+
+    @Test
+    public void testIsHorizontallyScrollingSameAsGiven() {
+        final TextView textView = mViewGroup.findViewById(
+                R.id.textView_scrollHorizontally_default);
+
+        textView.setHorizontallyScrolling(true);
+        assertTrue(textView.isHorizontallyScrolling());
+    }
+
+    @Test
+    public void testIsHorizontallyScrollingTrueToFalse() {
+        final TextView textView = mViewGroup.findViewById(
+                R.id.textView_scrollHorizontally_default);
+        textView.setHorizontallyScrolling(true);
+        assertTrue(textView.isHorizontallyScrolling());
+
+        textView.setHorizontallyScrolling(false);
+        assertFalse(textView.isHorizontallyScrolling());
+    }
+
+    @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.isHorizontallyScrolling());
+
+        final TextView textViewFalse = mViewGroup.findViewById(
+                R.id.textView_scrollHorizontally_false);
+        assertFalse(textViewFalse.isHorizontallyScrolling());
+    }
+
+    @Test
+    public void testIsHorizontallyScrollingSetInXML_returnTrueWhenSingleLineIsTrue() {
+        final TextView textViewDefault = mViewGroup.findViewById(
+                R.id.textView_scrollHorizontally_default_singleLine_true);
+        assertTrue(textViewDefault.isHorizontallyScrolling());
+
+        final TextView textViewTrue = mViewGroup.findViewById(
+                R.id.textView_scrollHorizontally_true_singleLine_true);
+        assertTrue(textViewTrue.isHorizontallyScrolling());
+
+        final TextView textViewFalse = mViewGroup.findViewById(
+                R.id.textView_scrollHorizontally_false_singleLine_true);
+        assertTrue(textViewFalse.isHorizontallyScrolling());
+    }
+
+    @Test
+    public void testIsHorizontallyScrollingSetInXML_returnGivenValueWhenSingleLineIsFalse() {
+        final TextView textViewDefault = mViewGroup.findViewById(
+                R.id.textView_scrollHorizontally_default_singleLine_false);
+        assertFalse(textViewDefault.isHorizontallyScrolling());
+
+        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.isHorizontallyScrolling());
+
+        final TextView textViewFalse = mViewGroup.findViewById(
+                R.id.textView_scrollHorizontally_false_singleLine_false);
+        assertFalse(textViewFalse.isHorizontallyScrolling());
+    }
+}
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..8c6e7e7 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;
@@ -3288,6 +3291,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 +4024,240 @@
         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(expected = NullPointerException.class)
+    public void testCursorDrawable_cannotBeSetToNull() {
+        new TextView(mActivity).setTextCursorDrawable(null);
+    }
+
+    @UiThreadTest
+    @Test(expected = IllegalArgumentException.class)
+    public void testCursorDrawable_cannotBeSetToZeroResId() {
+        new TextView(mActivity).setTextCursorDrawable(0);
+    }
+
+    @UiThreadTest
+    @Test(expected = IllegalArgumentException.class)
+    public void testCursorDrawable_cannotBeSetToNegativeResId() {
+        new TextView(mActivity).setTextCursorDrawable(-1);
+    }
+
+    @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 = IllegalArgumentException.class)
+    public void testSelectHandleDrawable_cannotBeSetToNegativeResId() {
+        new TextView(mActivity).setTextSelectHandle(-1);
+    }
+
+    @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 = IllegalArgumentException.class)
+    public void testSelectHandleDrawableLeft_cannotBeSetToNegativeResId() {
+        new TextView(mActivity).setTextSelectHandle(-1);
+    }
+
+    @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);
+    }
+
+    @UiThreadTest
+    @Test(expected = IllegalArgumentException.class)
+    public void testSelectHandleDrawableRight_cannotBeSetToNegativeResId() {
+        new TextView(mActivity).setTextSelectHandleRight(-1);
+    }
+
+    @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 +4314,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 +6552,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 +8284,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 a5d9db3..5e21c66 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
+}